// /* audisp-gmseal.c (plugin 主程序关键修正版)
//    主要修正：
//     - reconstruct_state_from_idx() 增加 idx vs audit.log 一致性检测；
//     - 当 idx 表示的 offset/行数大于日志实际文件时，执行从 audit.log 重新构建 idx（覆盖旧 idx）。
// */

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>
// #include "../audit-gm/alarm.c"

// #include "../../lib/gm_crypto.h"

// /* === 配置项（可按需修改） === */
// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass" /* 明文存储或按你已有流程 */

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 由于我们要“随时可验证”，这里不再按 100 行写一次，而是每行都刷新（可关掉） */
// #define ALWAYS_REFRESH_SEAL 1

// /* 守护日志 */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }
// static int hex2bin(const char *hex, unsigned char *out, size_t outlen)
// {
//     size_t hlen = strlen(hex);
//     if (hlen != outlen * 2)
//         return -1;
//     for (size_t i = 0; i < outlen; i++)
//     {
//         int hi = -1, lo = -1;
//         char c = hex[i * 2];
//         if (c >= '0' && c <= '9')
//             hi = c - '0';
//         else if (c >= 'a' && c <= 'f')
//             hi = c - 'a' + 10;
//         else if (c >= 'A' && c <= 'F')
//             hi = c - 'A' + 10;
//         c = hex[i * 2 + 1];
//         if (c >= '0' && c <= '9')
//             lo = c - '0';
//         else if (c >= 'a' && c <= 'f')
//             lo = c - 'a' + 10;
//         else if (c >= 'A' && c <= 'F')
//             lo = c - 'A' + 10;
//         if (hi < 0 || lo < 0)
//             return -1;
//         out[i] = (unsigned char)((hi << 4) | lo);
//     }
//     return 0;
// }

// /* Base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64)
//             BIO_free(b64);
//         if (mem)
//             BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     (void)BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* read PASS file */
// static char *read_password_file(const char *path)
// {
//     FILE *f = fopen(path, "r");
//     if (!f)
//         return NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n = getline(&line, &cap, f);
//     fclose(f);
//     if (n <= 0)
//     {
//         free(line);
//         return NULL;
//     }
//     while (n && (line[n - 1] == '\n' || line[n - 1] == '\r'))
//         line[--n] = 0;
//     return line;
// }

// /* compute SM3 hex of file */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* ===== 全局状态 ===== */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;

// static int g_need_seal = 0;
// static int g_run = 1;

// static void on_signal(int sig)
// {
//     if (sig == SIGTERM || sig == SIGINT)
//         g_run = 0;
//     if (sig == SIGHUP)
//         g_need_seal = 1;
// }

// /* helper: count lines in file */
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* helper: rebuild idx from current audit.log (覆盖 idx) */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf)
//         return -1;

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long offset = 0;
//     unsigned char H[32] = {0};

//     /* 打开临时 idx 文件，写入新 idx（原子替换） */
//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf)
//     {
//         fclose(lf);
//         return -1;
//     }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         /* compute lhash */
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         if (!c1 ||
//             EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, (size_t)n) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         /* compute chash = SM3(prev_H || line) */
//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32];
//         unsigned int hm = 0;
//         if (!c2 ||
//             EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, (size_t)n) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2)
//                 EVP_MD_CTX_free(c2);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);

//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         offset += (unsigned long long)n;
//     }

//     /* finish */
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);

//     /* atomically replace idx */
//     if (rename(tmppath, idxpath) != 0)
//     {
//         unlink(tmppath);
//         return -1;
//     }

//     /* set outputs */
//     memcpy(out_chain, H, 32);
//     if (out_lines)
//         *out_lines = ln;
//     if (out_offset)
//         *out_offset = offset;
//     return 0;
// }

// /* 从 idx 恢复状态（最后一行 chash、i、off）。如果 idx 与日志不一致（offset > file_size 或 last_i > file_lines），尝试重建 idx */
// static int reconstruct_state_from_idx(FILE *idx_fp)
// {
//     if (!idx_fp)
//         return -1;
//     rewind(idx_fp);
//     char buf[1024];
//     unsigned long long last_i = 0, last_off = 0;
//     char last_chash[65] = {0};
//     while (fgets(buf, sizeof(buf), idx_fp) != NULL)
//     {
//         unsigned long long i = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//         {
//             last_i = i;
//             last_off = off;
//             strncpy(last_chash, chex, sizeof(last_chash) - 1);
//         }
//     }

//     /* 如果 idx 为空，则尝试从 seal 恢复（你的现有逻辑可以保留） */
//     if (last_i == 0)
//     {
//         /* 尝试从 seal 恢复（如果存在） */
//         if (access(SEAL_PATH, R_OK) == 0)
//         {
//             unsigned long long seal_records = 0;
//             char seal_chain_hex[65] = {0};
//             /* 这里我们简单解析 seal 前两项（records,chain），不在此重复实现，默认外部实现 parse_seal_min */
//             /* 你的现有实现已有 parse_seal_min，比如 parse_seal_min(SEAL_PATH, &seal_records, seal_chain_hex) */
//             /* 为简洁，这里直接返回 0 表示初始化为空；真实场景建议实现从 seal 恢复 */
//         }
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* 若 idx 有最后记录，我们验证 idx 的 last_off 与 audit.log 文件大小是否一致 */
//     struct stat st;
//     if (stat(LOG_PATH, &st) == 0)
//     {
//         unsigned long long file_size = (unsigned long long)st.st_size;
//         unsigned long long file_lines = count_file_lines_simple(LOG_PATH);

//         /* 如果 last_off 大于文件大小或 last_i > file_lines，则说明 idx 与日志不一致（可能日志被截断或被清空） */
//         if ((unsigned long long)last_off > file_size || last_i > file_lines)
//         {
//             log_msg("idx inconsistency detected: idx last_i=%llu last_off=%llu but log file size=%llu lines=%llu. Rebuilding idx from current log.",
//                     last_i, last_off, file_size, file_lines);
//             /* alarm & attempt to rebuild idx from current log */
//             trigger_alarm("idx/log mismatch detected: rebuilding index from current audit.log (possible truncation or tampering)");

//             unsigned char new_chain[32];
//             unsigned long long new_lines = 0;
//             unsigned long long new_off = 0;
//             if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//             {
//                 /* replace g_chain/g_lines/g_offset accordingly */
//                 memcpy(g_chain, new_chain, 32);
//                 g_lines = new_lines;
//                 g_offset = new_off;

//                 /* reopen g_idx as updated */
//                 if (g_idx)
//                     fclose(g_idx);
//                 g_idx = fopen(IDX_PATH, "a+");
//                 if (!g_idx)
//                 {
//                     log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//                     return -1;
//                 }
//                 chmod(IDX_PATH, 0600);
//                 log_msg("rebuild idx done: lines=%llu offset=%llu", g_lines, g_offset);
//                 return 0;
//             }
//             else
//             {
//                 log_msg("rebuild_idx_from_log failed");
//                 /* fallthrough: we'll treat as empty chain to avoid using stale idx data */
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 return 0;
//             }
//         }
//     }
//     else
//     {
//         /* 无法 stat 日志，保守处理：重置 */
//         log_msg("stat %s failed: %s", LOG_PATH, strerror(errno));
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* 如果一切 OK，就把 last_chash 解析为 g_chain，设定 g_lines / g_offset */
//     if (hex2bin(last_chash, g_chain, 32) != 0)
//         return -1;
//     g_lines = last_i;
//     g_offset = last_off;
//     return 0;
// }

// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx)
//     {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_SH) != 0)
//     {
//         log_msg("flock(LOCK_SH) failed on idx: %s", strerror(errno));
//     }
//     if (reconstruct_state_from_idx(g_idx) != 0)
//     {
//         log_msg("failed to reconstruct state from idx");
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     flock(fd, LOCK_UN);
//     log_msg("reconstructed state: records=%llu offset=%llu",
//             (unsigned long long)g_lines, (unsigned long long)g_offset);
//     return 0;
// }

// /* 处理1行：lhash、chash，写 idx */
// static int gmseal_on_line(const char *line, size_t n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;

//     unsigned char lhash[32], newH[32];
//     unsigned int lml = 0, hlen = 0;

//     /* lhash = SM3(line) */
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line, n) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lml) != 1 || lml != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     /* chash = SM3(prev_chain || line) */
//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2)
//         return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line, n) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hlen) != 1 || hlen != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx)
//         return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;

//     if (flock(fd, LOCK_EX) != 0)
//     {
//         log_msg("flock(LOCK_EX) failed on idx: %s", strerror(errno));
//     }
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//                 (unsigned long long)g_lines,
//                 (unsigned long long)g_offset,
//                 lhex, chex) < 0)
//     {
//         log_msg("failed to write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += n;
//     return 0;
// }

// /* 写 seal（包含 index_sm3） */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* 读取 PASS */
//     char *secret = read_password_file(PASS_PATH);
//     if (!secret)
//     {
//         log_msg("read PASS_PATH failed");
//         return -1;
//     }
//     /* 读取 salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf)
//     {
//         log_msg("open SALT_PATH failed");
//         free(secret);
//         return -1;
//     }
//     char saltbuf[256];
//     if (!fgets(saltbuf, sizeof(saltbuf), sf))
//     {
//         fclose(sf);
//         free(secret);
//         return -1;
//     }
//     fclose(sf);
//     size_t slen = strlen(saltbuf);
//     while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//         saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES * 2)
//     {
//         log_msg("salt file length unexpected");
//         free(secret);
//         return -1;
//     }
//     for (size_t i = 0; i < SALT_BYTES; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             free(secret);
//             return -1;
//         }
//         salt[i] = (hi << 4) | lo;
//     }

//     /* PBKDF2-SM3 工衍 */
//     unsigned char derived[DERIVE_BYTES];
//     const EVP_MD *md = EVP_get_digestbyname("sm3");
//     if (!md)
//     {
//         log_msg("no sm3 available");
//         free(secret);
//         return -1;
//     }
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES,
//                            DERIVE_ITERATIONS, md, DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     char passhex[DERIVE_BYTES * 2 + 1];
//     hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret));
//     free(secret);

//     /* 解析 p12 */
//     FILE *pf = fopen(P12_PATH, "rb");
//     if (!pf)
//     {
//         log_msg("open P12_PATH failed");
//         return -1;
//     }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf, NULL);
//     fclose(pf);
//     if (!p12)
//     {
//         log_msg("d2i_PKCS12_fp failed");
//         return -1;
//     }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//     {
//         log_msg("PKCS12_parse failed");
//         PKCS12_free(p12);
//         return -1;
//     }
//     PKCS12_free(p12);

//     /* 证书 SM3 指纹 */
//     char fpr_hex[65];
//     {
//         unsigned char *der = NULL;
//         int derlen = i2d_X509(cert, &der);
//         if (derlen <= 0)
//         {
//             log_msg("i2d_X509 failed");
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char mdv[EVP_MAX_MD_SIZE];
//         unsigned int mdlen = 0;
//         if (!ctx)
//         {
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//             EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der);
//         hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     }

//     char chain_hex[65];
//     hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0)
//     {
//         log_msg("sm3 of idx failed");
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* 构造规范串（audit-verify 也按同样格式重新拼接） */
//     char *canon = NULL;
//     if (asprintf(&canon,
//                  "version=1\n"
//                  "hash=sm3\n"
//                  "sign=sm2\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%llu\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* SM2 签名（lib/gm_crypto.c 提供的国密接口） */
//     unsigned char *sig = NULL;
//     size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon),
//                     SM2_ID, &sig, &siglen) != 0)
//     {
//         log_msg("gm_sm2_sign failed");
//         free(canon);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     char *sigb64 = NULL;
//     sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* 证书 DER/Base64 */
//     unsigned char *der = NULL;
//     int derlen = i2d_X509(cert, &der);
//     char *certb64 = NULL;
//     if (derlen > 0)
//     {
//         certb64 = b64_encode(der, derlen);
//         OPENSSL_free(der);
//     }
//     else
//     {
//         log_msg("i2d_X509 failed");
//         free(canon);
//         free(sigb64);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     EVP_PKEY_free(pkey);
//     X509_free(cert);

//     /* 原子写 seal */
//     char tmp[PATH_MAX];
//     snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw)
//     {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n");
//     fprintf(sfw, "%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0)
//     {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }

//     free(canon);
//     free(sigb64);
//     free(certb64);

//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 主循环 */
// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);
//     signal(SIGHUP, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed");
//         gm_crypto_cleanup();
//         return 2;
//     }

//     /* 启动时即写一次 seal（哪怕当前没有新行），确保“随时可验证”。 */
//     if (gmseal_write_seal() != 0)
//         log_msg("initial seal write failed");

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;

//     while (g_run)
//     {
//         n = getline(&line, &cap, stdin);
//         if (n == -1)
//         {
//             if (feof(stdin))
//             {
//                 clearerr(stdin);
//                 usleep(200000); /* 200ms 等待后再试 */
//                 continue;
//             }
//             else if (errno == EINTR)
//             {
//                 continue;
//             }
//             else
//             {
//                 log_msg("getline error: %s", strerror(errno));
//                 break;
//             }
//         }
//         if (n > 0)
//         {
//             if (gmseal_on_line(line, (size_t)n) != 0)
//             {
//                 log_msg("gmseal_on_line failed for line #%llu",
//                         (unsigned long long)(g_lines + 1));
//             }
// #if ALWAYS_REFRESH_SEAL
//             /* 每行写完，即刷新 seal，保证“随时可验证” */
//             if (gmseal_write_seal() != 0)
//                 log_msg("gmseal_write_seal failed");
// #endif
//         }

//         if (g_need_seal)
//         {
//             g_need_seal = 0;
//             if (gmseal_write_seal() != 0)
//             {
//                 log_msg("gmseal_write_seal failed");
//             }
//         }
//     }

//     /* 退出前再写一次 seal */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");

//     if (g_idx)
//         fclose(g_idx);
//     if (line)
//         free(line);
//     gm_crypto_cleanup();
//     if (g_log)
//         fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

//----------------解决.isx  .seal和audit.log行数不一致问题-------
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// /* === 配置项（可按需修改） === */
// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass" /* 明文存储或按你已有流程 */

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 由于我们要“随时可验证”，这里不再按 100 行写一次，而是每行都刷新（可关掉） */
// #define ALWAYS_REFRESH_SEAL 1

// /* 守护日志 */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }
// static int hex2bin(const char *hex, unsigned char *out, size_t outlen)
// {
//     size_t hlen = strlen(hex);
//     if (hlen != outlen * 2)
//         return -1;
//     for (size_t i = 0; i < outlen; i++)
//     {
//         int hi = -1, lo = -1;
//         char c = hex[i * 2];
//         if (c >= '0' && c <= '9')
//             hi = c - '0';
//         else if (c >= 'a' && c <= 'f')
//             hi = c - 'a' + 10;
//         else if (c >= 'A' && c <= 'F')
//             hi = c - 'A' + 10;
//         c = hex[i * 2 + 1];
//         if (c >= '0' && c <= '9')
//             lo = c - '0';
//         else if (c >= 'a' && c <= 'f')
//             lo = c - 'a' + 10;
//         else if (c >= 'A' && c <= 'F')
//             lo = c - 'A' + 10;
//         if (hi < 0 || lo < 0)
//             return -1;
//         out[i] = (unsigned char)((hi << 4) | lo);
//     }
//     return 0;
// }

// /* Base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64)
//             BIO_free(b64);
//         if (mem)
//             BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     (void)BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* read PASS file */
// static char *read_password_file(const char *path)
// {
//     FILE *f = fopen(path, "r");
//     if (!f)
//         return NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n = getline(&line, &cap, f);
//     fclose(f);
//     if (n <= 0)
//     {
//         free(line);
//         return NULL;
//     }
//     while (n && (line[n - 1] == '\n' || line[n - 1] == '\r'))
//         line[--n] = 0;
//     return line;
// }

// /* compute SM3 hex of file */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* ===== 全局状态 ===== */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;

// static int g_need_seal = 0;
// static int g_run = 1;

// static void on_signal(int sig)
// {
//     if (sig == SIGTERM || sig == SIGINT)
//         g_run = 0;
//     if (sig == SIGHUP)
//         g_need_seal = 1;
// }

// /* helper: count lines in file */
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* helper: rebuild idx from current audit.log (覆盖 idx) */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf)
//         return -1;

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long offset = 0;
//     unsigned char H[32] = {0};

//     /* 打开临时 idx 文件，写入新 idx（原子替换） */
//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf)
//     {
//         fclose(lf);
//         return -1;
//     }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         /* compute lhash */
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         if (!c1 ||
//             EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, (size_t)n) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         /* compute chash = SM3(prev_H || line) */
//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32];
//         unsigned int hm = 0;
//         if (!c2 ||
//             EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, (size_t)n) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2)
//                 EVP_MD_CTX_free(c2);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);

//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         offset += (unsigned long long)n;
//     }

//     /* finish */
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);

//     /* atomically replace idx */
//     if (rename(tmppath, idxpath) != 0)
//     {
//         unlink(tmppath);
//         return -1;
//     }

//     /* set outputs */
//     memcpy(out_chain, H, 32);
//     if (out_lines)
//         *out_lines = ln;
//     if (out_offset)
//         *out_offset = offset;
//     return 0;
// }

// /* 从 idx 恢复状态（最后一行 chash、i、off）。如果 idx 与日志不一致（offset > file_size 或 last_i > file_lines），尝试重建 idx */
// static int reconstruct_state_from_idx(FILE *idx_fp)
// {
//     if (!idx_fp)
//         return -1;
//     rewind(idx_fp);
//     char buf[1024];
//     unsigned long long last_i = 0, last_off = 0;
//     char last_chash[65] = {0};
//     while (fgets(buf, sizeof(buf), idx_fp) != NULL)
//     {
//         unsigned long long i = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//         {
//             last_i = i;
//             last_off = off;
//             strncpy(last_chash, chex, sizeof(last_chash) - 1);
//         }
//     }

//     /* 如果 idx 为空，则初始化为空链（可从 seal 恢复，略） */
//     if (last_i == 0)
//     {
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* 若 idx 有最后记录，我们验证 idx 的 last_off 与 audit.log 文件大小是否一致 */
//     struct stat st;
//     if (stat(LOG_PATH, &st) == 0)
//     {
//         unsigned long long file_size = (unsigned long long)st.st_size;
//         unsigned long long file_lines = count_file_lines_simple(LOG_PATH);

//         /* 如果 last_off 大于文件大小或 last_i > file_lines，则说明 idx 与日志不一致（可能日志被截断或被清空） */
//         if ((unsigned long long)last_off > file_size || last_i > file_lines)
//         {
//             log_msg("idx inconsistency detected: idx last_i=%llu last_off=%llu but log file size=%llu lines=%llu. Rebuilding idx from current log.",
//                     last_i, last_off, file_size, file_lines);
//             trigger_alarm("idx/log mismatch detected: rebuilding index from current audit.log (possible truncation or tampering)");

//             unsigned char new_chain[32];
//             unsigned long long new_lines = 0;
//             unsigned long long new_off = 0;
//             if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//             {
//                 /* replace g_chain/g_lines/g_offset accordingly */
//                 memcpy(g_chain, new_chain, 32);
//                 g_lines = new_lines;
//                 g_offset = new_off;

//                 /* reopen g_idx as updated */
//                 if (idx_fp)
//                     fclose(idx_fp);
//                 g_idx = fopen(IDX_PATH, "a+");
//                 if (!g_idx)
//                 {
//                     log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//                     return -1;
//                 }
//                 chmod(IDX_PATH, 0600);
//                 log_msg("rebuild idx done: lines=%llu offset=%llu", g_lines, g_offset);
//                 return 0;
//             }
//             else
//             {
//                 log_msg("rebuild_idx_from_log failed");
//                 /* fallthrough: we'll treat as empty chain to avoid using stale idx data */
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 return 0;
//             }
//         }
//     }
//     else
//     {
//         /* 无法 stat 日志，保守处理：重置 */
//         log_msg("stat %s failed: %s", LOG_PATH, strerror(errno));
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* 如果一切 OK，就把 last_chash 解析为 g_chain，设定 g_lines / g_offset */
//     if (hex2bin(last_chash, g_chain, 32) != 0)
//         return -1;
//     g_lines = last_i;
//     g_offset = last_off;
//     return 0;
// }

// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx)
//     {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_SH) != 0)
//     {
//         log_msg("flock(LOCK_SH) failed on idx: %s", strerror(errno));
//     }
//     if (reconstruct_state_from_idx(g_idx) != 0)
//     {
//         log_msg("failed to reconstruct state from idx");
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     flock(fd, LOCK_UN);
//     log_msg("reconstructed state: records=%llu offset=%llu",
//             (unsigned long long)g_lines, (unsigned long long)g_offset);
//     return 0;
// }

// /* 处理1行：lhash、chash，写 idx */
// static int gmseal_on_line(const char *line, size_t n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;

//     unsigned char lhash[32], newH[32];
//     unsigned int lml = 0, hlen = 0;

//     /* lhash = SM3(line) */
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line, n) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lml) != 1 || lml != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     /* chash = SM3(prev_chain || line) */
//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2)
//         return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line, n) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hlen) != 1 || hlen != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx)
//         return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;

//     if (flock(fd, LOCK_EX) != 0)
//     {
//         log_msg("flock(LOCK_EX) failed on idx: %s", strerror(errno));
//     }
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//                 (unsigned long long)g_lines,
//                 (unsigned long long)g_offset,
//                 lhex, chex) < 0)
//     {
//         log_msg("failed to write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += n;
//     return 0;
// }

// /* 写 seal（包含 index_sm3）
//    关键增强：在签名前强制保证 .log/.idx 行数一致；必要时重建 idx；seal.records 使用真实 log 行数
// */
// static int gmseal_write_seal(void)
// {
//     /* 1) 检查并修复 .idx 与 .log 的不一致（在做任何耗时/敏感操作之前） */
//     unsigned long long real_lines = count_file_lines_simple(LOG_PATH);
//     unsigned long long idx_lines = count_file_lines_simple(IDX_PATH);

//     if (idx_lines != real_lines)
//     {
//         log_msg("detected mismatch before seal: log=%llu lines, idx=%llu lines => attempting rebuild", real_lines, idx_lines);
//         unsigned char new_chain[32];
//         unsigned long long new_lines = 0, new_off = 0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//         {
//             /* 更新内存状态 */
//             memcpy(g_chain, new_chain, 32);
//             g_lines = new_lines;
//             g_offset = new_off;

//             /* 重新打开 g_idx（a+）以便追加后续记录 */
//             if (g_idx)
//                 fclose(g_idx);
//             g_idx = fopen(IDX_PATH, "a+");
//             if (!g_idx)
//             {
//                 log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//                 /* 继续，但会影响写 idx 的能力 */
//             }
//             else
//             {
//                 chmod(IDX_PATH, 0600);
//             }
//             log_msg("rebuild idx succeeded: lines=%llu offset=%llu", g_lines, g_offset);
//         }
//         else
//         {
//             log_msg("rebuild idx failed, will continue but seal/index may be inconsistent");
//             trigger_alarm("gmseal: rebuild_idx_from_log failed during seal");
//             /* 继续下去：我们仍然要写 seal，但seal会根据 real_lines 写 records */
//         }
//     }

//     /* 2) 确保 g_lines 与 log 的真实行数一致（seal.records 使用真实行数） */
//     /* Important: do not blindly trust g_lines counter across restarts; use real file count for seal */
//     real_lines = count_file_lines_simple(LOG_PATH);
//     g_lines = real_lines;

//     /* 之后再进行 p12 解密 / 签名等耗时工作 */
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* 读取 PASS */
//     char *secret = read_password_file(PASS_PATH);
//     if (!secret)
//     {
//         log_msg("read PASS_PATH failed");
//         return -1;
//     }
//     /* 读取 salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf)
//     {
//         log_msg("open SALT_PATH failed");
//         free(secret);
//         return -1;
//     }
//     char saltbuf[256];
//     if (!fgets(saltbuf, sizeof(saltbuf), sf))
//     {
//         fclose(sf);
//         free(secret);
//         return -1;
//     }
//     fclose(sf);
//     size_t slen = strlen(saltbuf);
//     while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//         saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES * 2)
//     {
//         log_msg("salt file length unexpected");
//         free(secret);
//         return -1;
//     }
//     for (size_t i = 0; i < SALT_BYTES; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             free(secret);
//             return -1;
//         }
//         salt[i] = (hi << 4) | lo;
//     }

//     /* PBKDF2-SM3 工衍 */
//     unsigned char derived[DERIVE_BYTES];
//     const EVP_MD *md = EVP_get_digestbyname("sm3");
//     if (!md)
//     {
//         log_msg("no sm3 available");
//         free(secret);
//         return -1;
//     }
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES,
//                            DERIVE_ITERATIONS, md, DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     char passhex[DERIVE_BYTES * 2 + 1];
//     hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret));
//     free(secret);

//     /* 解析 p12 */
//     FILE *pf = fopen(P12_PATH, "rb");
//     if (!pf)
//     {
//         log_msg("open P12_PATH failed");
//         return -1;
//     }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf, NULL);
//     fclose(pf);
//     if (!p12)
//     {
//         log_msg("d2i_PKCS12_fp failed");
//         return -1;
//     }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//     {
//         log_msg("PKCS12_parse failed");
//         PKCS12_free(p12);
//         return -1;
//     }
//     PKCS12_free(p12);

//     /* 证书 SM3 指纹 */
//     char fpr_hex[65];
//     {
//         unsigned char *der = NULL;
//         int derlen = i2d_X509(cert, &der);
//         if (derlen <= 0)
//         {
//             log_msg("i2d_X509 failed");
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char mdv[EVP_MAX_MD_SIZE];
//         unsigned int mdlen = 0;
//         if (!ctx)
//         {
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//             EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der);
//         hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     }

//     char chain_hex[65];
//     hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     /* 计算 idx 的 SM3（如果 idx 不存在或计算失败就报错） */
//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0)
//     {
//         log_msg("sm3 of idx failed");
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* 构造规范串（audit-verify 也按同样格式重新拼接）
//        注意：使用 g_lines（已被设为 real_lines）确保 records 与 log 一致 */
//     char *canon = NULL;
//     if (asprintf(&canon,
//                  "version=1\n"
//                  "hash=sm3\n"
//                  "sign=sm2\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%llu\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* SM2 签名（lib/gm_crypto.c 提供的国密接口） */
//     unsigned char *sig = NULL;
//     size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon),
//                     SM2_ID, &sig, &siglen) != 0)
//     {
//         log_msg("gm_sm2_sign failed");
//         free(canon);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     char *sigb64 = NULL;
//     sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* 证书 DER/Base64 */
//     unsigned char *der = NULL;
//     int derlen = i2d_X509(cert, &der);
//     char *certb64 = NULL;
//     if (derlen > 0)
//     {
//         certb64 = b64_encode(der, derlen);
//         OPENSSL_free(der);
//     }
//     else
//     {
//         log_msg("i2d_X509 failed");
//         free(canon);
//         free(sigb64);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     EVP_PKEY_free(pkey);
//     X509_free(cert);

//     /* 原子写 seal */
//     char tmp[PATH_MAX];
//     snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw)
//     {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n");
//     fprintf(sfw, "%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0)
//     {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }

//     free(canon);
//     free(sigb64);
//     free(certb64);

//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 主循环 */
// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);
//     signal(SIGHUP, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed");
//         gm_crypto_cleanup();
//         return 2;
//     }

//     /* 启动时即写一次 seal（哪怕当前没有新行），确保“随时可验证”。 */
//     if (gmseal_write_seal() != 0)
//         log_msg("initial seal write failed");

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;

//     while (g_run)
//     {
//         n = getline(&line, &cap, stdin);
//         if (n == -1)
//         {
//             if (feof(stdin))
//             {
//                 clearerr(stdin);
//                 usleep(200000); /* 200ms 等待后再试 */
//                 continue;
//             }
//             else if (errno == EINTR)
//             {
//                 continue;
//             }
//             else
//             {
//                 log_msg("getline error: %s", strerror(errno));
//                 break;
//             }
//         }
//         if (n > 0)
//         {
//             if (gmseal_on_line(line, (size_t)n) != 0)
//             {
//                 log_msg("gmseal_on_line failed for line #%llu",
//                         (unsigned long long)(g_lines + 1));
//             }
// #if ALWAYS_REFRESH_SEAL
//             /* 每行写完，即刷新 seal，保证“随时可验证” */
//             if (gmseal_write_seal() != 0)
//                 log_msg("gmseal_write_seal failed");
// #endif
//         }

//         if (g_need_seal)
//         {
//             g_need_seal = 0;
//             if (gmseal_write_seal() != 0)
//             {
//                 log_msg("gmseal_write_seal failed");
//             }
//         }
//     }

//     /* 退出前再写一次 seal */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");

//     if (g_idx)
//         fclose(g_idx);
//     if (line)
//         free(line);
//     gm_crypto_cleanup();
//     if (g_log)
//         fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

//----------------解决.isx  .seal和audit.log行数不一致问题  版本3-------
/* gmseal-plugin.c (modified)
   Tail /var/log/audit/audit.log, compute lhash/chash with normalized lines (trim \r\n),
   maintain audit.log.idx and audit.log.seal atomically.
*/

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <sys/inotify.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>
// #include "../audit-gm/alarm.c"

// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试时可保持1，生产可改为每N行） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64)
//             BIO_free(b64);
//         if (mem)
//             BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* read PASS file (single-line password) */
// static char *read_password_file(const char *path)
// {
//     FILE *f = fopen(path, "r");
//     if (!f)
//         return NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n = getline(&line, &cap, f);
//     fclose(f);
//     if (n <= 0)
//     {
//         free(line);
//         return NULL;
//     }
//     while (n && (line[n - 1] == '\n' || line[n - 1] == '\r'))
//         line[--n] = 0;
//     return line;
// }

// /* SM3 file hex */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* trim trailing \r and \n in place; return trimmed length */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     /* do not modify buffer content except trimming length */
//     return len;
// }

// /* count lines simple */
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx from log (atomic replace). Use normalized content for lhash/chash but keep offset accounting using original n (including newline). */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf)
//     {
//         fclose(lf);
//         return -1;
//     }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         /* normalize: compute hash over content without trailing CR/LF */
//         size_t normlen = normalize_line(line, (size_t)n);

//         /* lhash = SM3(normalized_line) */
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 || EVP_DigestUpdate(c1, line, normlen) != 1 || EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         /* chash = SM3(prev_H || normalized_line) */
//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32];
//         unsigned int hm = 0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 || EVP_DigestUpdate(c2, H, sizeof(H)) != 1 || EVP_DigestUpdate(c2, line, normlen) != 1 || EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2)
//                 EVP_MD_CTX_free(c2);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         offset += (unsigned long long)n; /* offset counts original bytes including newline */
//     }

//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);

//     if (rename(tmppath, idxpath) != 0)
//     {
//         unlink(tmppath);
//         return -1;
//     }

//     if (out_chain)
//         memcpy(out_chain, H, 32);
//     if (out_lines)
//         *out_lines = ln;
//     if (out_offset)
//         *out_offset = offset;
//     return 0;
// }

// /* global state */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;
// static int g_need_seal = 0;

// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
//     if (sig == SIGHUP)
//         g_need_seal = 1;
// }

// /* reconstruct state from idx; if inconsistency, attempt rebuild */
// static int reconstruct_state_from_idx(FILE *idx_fp)
// {
//     if (!idx_fp)
//         return -1;
//     rewind(idx_fp);
//     char buf[1024];
//     unsigned long long last_i = 0, last_off = 0;
//     char last_chash[65] = {0};
//     while (fgets(buf, sizeof(buf), idx_fp) != NULL)
//     {
//         unsigned long long i = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//         {
//             last_i = i;
//             last_off = off;
//             strncpy(last_chash, chex, sizeof(last_chash) - 1);
//         }
//     }

//     if (last_i == 0)
//     {
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     struct stat st;
//     if (stat(LOG_PATH, &st) == 0)
//     {
//         unsigned long long file_size = (unsigned long long)st.st_size;
//         unsigned long long file_lines = count_file_lines_simple(LOG_PATH);
//         if (last_off > file_size || last_i > file_lines)
//         {
//             log_msg("idx inconsistency: idx last_i=%llu last_off=%llu vs log size=%llu lines=%llu. Rebuild idx.", last_i, last_off, file_size, file_lines);
//             trigger_alarm("idx/log mismatch detected: rebuilding index from current audit.log (possible truncation or rotation)");
//             unsigned char new_chain[32];
//             unsigned long long new_lines = 0, new_off = 0;
//             if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//             {
//                 memcpy(g_chain, new_chain, 32);
//                 g_lines = new_lines;
//                 g_offset = new_off;
//                 if (g_idx)
//                     fclose(g_idx);
//                 g_idx = fopen(IDX_PATH, "a+");
//                 if (!g_idx)
//                 {
//                     log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//                     return -1;
//                 }
//                 chmod(IDX_PATH, 0600);
//                 log_msg("rebuild idx done: lines=%llu offset=%llu", g_lines, g_offset);
//                 return 0;
//             }
//             else
//             {
//                 log_msg("rebuild_idx_from_log failed");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 return 0;
//             }
//         }
//     }
//     else
//     {
//         log_msg("stat %s failed: %s", LOG_PATH, strerror(errno));
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* parse last_chash into g_chain */
//     unsigned char bin[32];
//     for (int i = 0; i < 32; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&last_chash[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             memset(g_chain, 0, 32);
//             g_lines = 0;
//             g_offset = 0;
//             return -1;
//         }
//         bin[i] = (hi << 4) | lo;
//     }
//     memcpy(g_chain, bin, 32);
//     g_lines = last_i;
//     g_offset = last_off;
//     return 0;
// }

// /* initialize */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx)
//     {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_SH) != 0)
//         log_msg("flock(LOCK_SH) failed: %s", strerror(errno));
//     if (reconstruct_state_from_idx(g_idx) != 0)
//     {
//         log_msg("failed reconstruct_state_from_idx");
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     flock(fd, LOCK_UN);
//     log_msg("reconstructed state: records=%llu offset=%llu", g_lines, g_offset);
//     return 0;
// }

// /* on new line read from log file: compute lhash/chash with normalized bytes and append idx */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     /* normalize */
//     size_t normlen = normalize_line(line_buf, raw_n);

//     unsigned char lhash[32], newH[32];
//     unsigned int lml = 0, hlen = 0;

//     /* lhash = SM3(normalized_line) */
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 || EVP_DigestUpdate(ctx, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx, lhash, &lml) != 1 || lml != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     /* chash = SM3(prev_chain || normalized_line) */
//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2)
//         return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 || EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 || EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx2, newH, &hlen) != 1 || hlen != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx)
//         return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     char *secret = read_password_file(PASS_PATH);
//     if (!secret)
//     {
//         log_msg("read PASS_PATH failed");
//         return -1;
//     }

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf)
//     {
//         log_msg("open SALT_PATH failed");
//         free(secret);
//         return -1;
//     }
//     char saltbuf[256];
//     if (!fgets(saltbuf, sizeof(saltbuf), sf))
//     {
//         fclose(sf);
//         free(secret);
//         return -1;
//     }
//     fclose(sf);
//     size_t slen = strlen(saltbuf);
//     while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//         saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES * 2)
//     {
//         log_msg("salt file length unexpected");
//         free(secret);
//         return -1;
//     }
//     for (size_t i = 0; i < SALT_BYTES; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             free(secret);
//             return -1;
//         }
//         salt[i] = (hi << 4) | lo;
//     }

//     /* PBKDF2-SM3 */
//     unsigned char derived[DERIVE_BYTES];
//     const EVP_MD *md = EVP_get_digestbyname("sm3");
//     if (!md)
//     {
//         log_msg("no sm3 available");
//         free(secret);
//         return -1;
//     }
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, md, DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     char passhex[DERIVE_BYTES * 2 + 1];
//     hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret));
//     free(secret);

//     /* load p12 */
//     FILE *pf = fopen(P12_PATH, "rb");
//     if (!pf)
//     {
//         log_msg("open P12_PATH failed");
//         return -1;
//     }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf, NULL);
//     fclose(pf);
//     if (!p12)
//     {
//         log_msg("d2i_PKCS12_fp failed");
//         return -1;
//     }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//     {
//         log_msg("PKCS12_parse failed");
//         PKCS12_free(p12);
//         return -1;
//     }
//     PKCS12_free(p12);

//     /* cert fingerprint */
//     char fpr_hex[65];
//     {
//         unsigned char *der = NULL;
//         int derlen = i2d_X509(cert, &der);
//         if (derlen <= 0)
//         {
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char mdv[EVP_MAX_MD_SIZE];
//         unsigned int mdlen = 0;
//         if (!ctx)
//         {
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der);
//         hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     }

//     char chain_hex[65];
//     hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0)
//     {
//         log_msg("sm3 of idx failed");
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* build canonical string */
//     char *canon = NULL;
//     if (asprintf(&canon,
//                  "version=1\n"
//                  "hash=sm3\n"
//                  "sign=sm2\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%llu\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* sign using gm_sm2_sign (from lib/gm_crypto) */
//     unsigned char *sig = NULL;
//     size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0)
//     {
//         log_msg("gm_sm2_sign failed");
//         free(canon);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* cert DER base64 */
//     unsigned char *der = NULL;
//     int derlen = i2d_X509(cert, &der);
//     char *certb64 = NULL;
//     if (derlen > 0)
//     {
//         certb64 = b64_encode(der, derlen);
//         OPENSSL_free(der);
//     }
//     else
//     {
//         log_msg("i2d_X509 failed");
//         free(canon);
//         free(sigb64);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     EVP_PKEY_free(pkey);
//     X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX];
//     snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw)
//     {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n");
//     fprintf(sfw, "%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0)
//     {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }

//     free(canon);
//     free(sigb64);
//     free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* Tail the log file: open LOG_PATH, seek to current g_offset, then loop reading appended lines */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0)
//         {
//             /* file may not exist yet; sleep and retry */
//             sleep(1);
//             if (!g_run)
//                 break;
//             continue;
//         }

//         /* open log if not open */
//         if (!lf)
//         {
//             lf = fopen(LOG_PATH, "rb");
//             if (!lf)
//             {
//                 sleep(1);
//                 continue;
//             }
//             /* if g_offset is non-zero and file smaller -> file rotated or truncated -> rebuild idx */
//             if ((unsigned long long)st.st_size < g_offset)
//             {
//                 log_msg("log truncated/rotated: file size %llu < g_offset %llu; rebuilding idx", (unsigned long long)st.st_size, g_offset);
//                 trigger_alarm("Audit log truncated/rotated: rebuilding index");
//                 unsigned char new_chain[32];
//                 unsigned long long new_lines = 0, new_off = 0;
//                 if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//                 {
//                     memcpy(g_chain, new_chain, 32);
//                     g_lines = new_lines;
//                     g_offset = new_off;
//                     if (g_idx)
//                     {
//                         fclose(g_idx);
//                     }
//                     g_idx = fopen(IDX_PATH, "a+");
//                 }
//                 else
//                 {
//                     log_msg("rebuild failed");
//                     memset(g_chain, 0, 32);
//                     g_lines = 0;
//                     g_offset = 0;
//                 }
//             }
//             /* seek to offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0)
//             {
//                 /* fallback to end */
//                 fseeko(lf, 0, SEEK_END);
//             }
//         }

//         /* read lines appended */
//         while ((n = getline(&line, &cap, lf)) != -1)
//         {
//             if (n > 0)
//             {
//                 if (gmseal_on_line(line, (size_t)n) != 0)
//                 {
//                     log_msg("gmseal_on_line failed for next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                 }
//                 if (ALWAYS_REFRESH_SEAL)
//                 {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run)
//                 break;
//         }

//         if (!g_run)
//             break;

//         /* EOF: sleep then continue polling. On rotation/truncation, close and reopen to pick up new file/inode */
//         clearerr(lf);
//         /* check if file inode changed */
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0)
//         { /* can't stat -> close and retry */
//             fclose(lf);
//             lf = NULL;
//             sleep(1);
//             continue;
//         }
//         if (st2.st_ino != st.st_ino)
//         {
//             /* rotated */
//             fclose(lf);
//             lf = NULL;
//             log_msg("audit.log rotated (inode changed) - will reopen");
//             sleep(1);
//             continue;
//         }
//         /* no rotation - just sleep shortly */
//         usleep(100000); /* 100ms */
//     }

//     if (line)
//         free(line);
//     if (lf)
//         fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);
//     signal(SIGHUP, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed");
//         gm_crypto_cleanup();
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0)
//         log_msg("initial seal write failed");

//     /* main tail loop */
//     tail_and_process();

//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx)
//         fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log)
//         fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

//----------------解决.isx  .seal和audit.log行数不一致问题  版本4-------

/* ---------- gmseal-plugin.c (修正版) ---------- */
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>
// #include "../audit-gm/alarm.c"

// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试时可保持1，生产可改为每N行） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64)
//             BIO_free(b64);
//         if (mem)
//             BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* read PASS file (single-line password) */
// static char *read_password_file(const char *path)
// {
//     FILE *f = fopen(path, "r");
//     if (!f)
//         return NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n = getline(&line, &cap, f);
//     fclose(f);
//     if (n <= 0)
//     {
//         free(line);
//         return NULL;
//     }
//     while (n && (line[n - 1] == '\n' || line[n - 1] == '\r'))
//         line[--n] = 0;
//     return line;
// }

// /* SM3 file hex */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* trim trailing \r and \n in place; return trimmed length */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     /* do not modify buffer content except trimming length */
//     return len;
// }

// /* count lines simple */
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx from log (atomic replace). Use normalized content for lhash/chash but keep offset accounting using original n (including newline). */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf)
//     {
//         fclose(lf);
//         return -1;
//     }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         /* normalize: compute hash over content without trailing CR/LF */
//         size_t normlen = normalize_line(line, (size_t)n);

//         /* lhash = SM3(normalized_line) */
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 || EVP_DigestUpdate(c1, line, normlen) != 1 || EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         /* chash = SM3(prev_H || normalized_line) */
//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32];
//         unsigned int hm = 0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 || EVP_DigestUpdate(c2, H, sizeof(H)) != 1 || EVP_DigestUpdate(c2, line, normlen) != 1 || EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2)
//                 EVP_MD_CTX_free(c2);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         offset += (unsigned long long)n; /* offset counts original bytes including newline */
//     }

//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);

//     if (rename(tmppath, idxpath) != 0)
//     {
//         unlink(tmppath);
//         return -1;
//     }

//     if (out_chain)
//         memcpy(out_chain, H, 32);
//     if (out_lines)
//         *out_lines = ln;
//     if (out_offset)
//         *out_offset = offset;
//     return 0;
// }

// /* global state */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;
// static int g_need_seal = 0;

// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
//     if (sig == SIGHUP)
//         g_need_seal = 1;
// }

// /* reconstruct state from idx; if inconsistency, attempt rebuild.
//    IMPORTANT: After reading idx tail we verify that the chain computed from audit.log first last_i lines equals last_chash.
//    If not, rebuild idx from log. */
// static int reconstruct_state_from_idx(FILE *idx_fp)
// {
//     if (!idx_fp)
//         return -1;
//     rewind(idx_fp);
//     char buf[1024];
//     unsigned long long last_i = 0, last_off = 0;
//     char last_chash[65] = {0};
//     while (fgets(buf, sizeof(buf), idx_fp) != NULL)
//     {
//         unsigned long long i = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//         {
//             last_i = i;
//             last_off = off;
//             strncpy(last_chash, chex, sizeof(last_chash) - 1);
//         }
//     }

//     if (last_i == 0)
//     {
//         /* empty idx */
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* verify index tail against actual audit.log first last_i lines */
//     struct stat st;
//     if (stat(LOG_PATH, &st) == 0)
//     {
//         unsigned long long file_size = (unsigned long long)st.st_size;
//         unsigned long long file_lines = count_file_lines_simple(LOG_PATH);

//         /* if idx claims more lines or offset than actual -> rebuild */
//         if (last_off > file_size || last_i > file_lines)
//         {
//             log_msg("idx inconsistency: idx last_i=%llu last_off=%llu vs log size=%llu lines=%llu. Rebuild idx.",
//                     last_i, last_off, file_size, file_lines);
//             trigger_alarm("idx/log mismatch detected: rebuilding index from current audit.log (possible truncation or rotation)");
//             unsigned char new_chain[32];
//             unsigned long long new_lines = 0, new_off = 0;
//             if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//             {
//                 memcpy(g_chain, new_chain, 32);
//                 g_lines = new_lines;
//                 g_offset = new_off;
//                 return 0;
//             }
//             else
//             {
//                 /* fallback to empty state */
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 return 0;
//             }
//         }

//         /* recompute chain over first last_i normalized lines and compare to last_chash */
//         unsigned char chain_bin[32];
//         unsigned long long scanned = 0;
//         if (last_i > 0)
//         {
//             /* compute chain using same logic as rebuild_idx_from_log (normalize) */
//             const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//             if (!sm3) return -1;
//             FILE *lf = fopen(LOG_PATH, "rb");
//             if (!lf) return -1;
//             char *line = NULL; size_t cap = 0; ssize_t n;
//             unsigned char H[32] = {0};
//             unsigned long long cnt = 0;
//             while (cnt < last_i && (n = getline(&line, &cap, lf)) != -1)
//             {
//                 size_t normlen = normalize_line(line, (size_t)n);
//                 EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//                 unsigned int hm = 0;
//                 unsigned char newH[32];
//                 if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 || EVP_DigestUpdate(c2, H, sizeof(H)) != 1 || EVP_DigestUpdate(c2, line, normlen) != 1 || EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//                 {
//                     if (c2) EVP_MD_CTX_free(c2);
//                     fclose(lf); free(line);
//                     return -1;
//                 }
//                 EVP_MD_CTX_free(c2);
//                 memcpy(H, newH, 32);
//                 cnt++;
//             }
//             free(line);
//             fclose(lf);
//             memcpy(chain_bin, H, 32);
//             scanned = cnt;
//         }
//         else
//         {
//             memset(chain_bin, 0, 32);
//             scanned = 0;
//         }
//         /* hex-ify */
//         char chain_hex[65]; hexdump(chain_bin, 32, chain_hex, sizeof(chain_hex));
//         if (strcmp(chain_hex, last_chash) != 0)
//         {
//             log_msg("idx tail chash doesn't match audit.log chain: idx_chash=%s recomputed=%s. Rebuild idx.", last_chash, chain_hex);
//             trigger_alarm("idx/log chash mismatch detected: rebuilding index from current audit.log");
//             unsigned char new_chain[32];
//             unsigned long long new_lines = 0, new_off = 0;
//             if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//             {
//                 memcpy(g_chain, new_chain, 32);
//                 g_lines = new_lines;
//                 g_offset = new_off;
//                 return 0;
//             }
//             else
//             {
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 return 0;
//             }
//         }
//     }
//     else
//     {
//         log_msg("stat %s failed: %s", LOG_PATH, strerror(errno));
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* parse last_chash into g_chain */
//     unsigned char bin[32];
//     for (int i = 0; i < 32; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&last_chash[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             memset(g_chain, 0, 32);
//             g_lines = 0;
//             g_offset = 0;
//             return -1;
//         }
//         bin[i] = (hi << 4) | lo;
//     }
//     memcpy(g_chain, bin, 32);
//     g_lines = last_i;
//     g_offset = last_off;
//     return 0;
// }

// /* initialize */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx)
//     {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_SH) != 0)
//         log_msg("flock(LOCK_SH) failed: %s", strerror(errno));
//     if (reconstruct_state_from_idx(g_idx) != 0)
//     {
//         log_msg("failed reconstruct_state_from_idx");
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     flock(fd, LOCK_UN);
//     log_msg("reconstructed state: records=%llu offset=%llu", g_lines, g_offset);
//     return 0;
// }

// /* on new line read from log file: compute lhash/chash with normalized bytes and append idx */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     /* normalize */
//     size_t normlen = normalize_line(line_buf, raw_n);

//     unsigned char lhash[32], newH[32];
//     unsigned int lml = 0, hlen = 0;

//     /* lhash = SM3(normalized_line) */
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 || EVP_DigestUpdate(ctx, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx, lhash, &lml) != 1 || lml != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     /* chash = SM3(prev_chain || normalized_line) */
//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2)
//         return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 || EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 || EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx2, newH, &hlen) != 1 || hlen != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx)
//         return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     char *secret = read_password_file(PASS_PATH);
//     if (!secret)
//     {
//         log_msg("read PASS_PATH failed");
//         return -1;
//     }

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf)
//     {
//         log_msg("open SALT_PATH failed");
//         free(secret);
//         return -1;
//     }
//     char saltbuf[256];
//     if (!fgets(saltbuf, sizeof(saltbuf), sf))
//     {
//         fclose(sf);
//         free(secret);
//         return -1;
//     }
//     fclose(sf);
//     size_t slen = strlen(saltbuf);
//     while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//         saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES * 2)
//     {
//         log_msg("salt file length unexpected");
//         free(secret);
//         return -1;
//     }
//     for (size_t i = 0; i < SALT_BYTES; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             free(secret);
//             return -1;
//         }
//         salt[i] = (hi << 4) | lo;
//     }

//     /* PBKDF2-SM3 */
//     unsigned char derived[DERIVE_BYTES];
//     const EVP_MD *md = EVP_get_digestbyname("sm3");
//     if (!md)
//     {
//         log_msg("no sm3 available");
//         free(secret);
//         return -1;
//     }
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, md, DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     char passhex[DERIVE_BYTES * 2 + 1];
//     hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret));
//     free(secret);

//     /* load p12 */
//     FILE *pf = fopen(P12_PATH, "rb");
//     if (!pf)
//     {
//         log_msg("open P12_PATH failed");
//         return -1;
//     }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf, NULL);
//     fclose(pf);
//     if (!p12)
//     {
//         log_msg("d2i_PKCS12_fp failed");
//         return -1;
//     }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//     {
//         log_msg("PKCS12_parse failed");
//         PKCS12_free(p12);
//         return -1;
//     }
//     PKCS12_free(p12);

//     /* cert fingerprint */
//     char fpr_hex[65];
//     {
//         unsigned char *der = NULL;
//         int derlen = i2d_X509(cert, &der);
//         if (derlen <= 0)
//         {
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char mdv[EVP_MAX_MD_SIZE];
//         unsigned int mdlen = 0;
//         if (!ctx)
//         {
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der);
//         hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     }

//     char chain_hex[65];
//     hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0)
//     {
//         log_msg("sm3 of idx failed");
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* build canonical string */
//     char *canon = NULL;
//     if (asprintf(&canon,
//                  "version=1\n"
//                  "hash=sm3\n"
//                  "sign=sm2\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%llu\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* sign using gm_sm2_sign (from lib/gm_crypto) */
//     unsigned char *sig = NULL;
//     size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0)
//     {
//         log_msg("gm_sm2_sign failed");
//         free(canon);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* cert DER base64 */
//     unsigned char *der = NULL;
//     int derlen = i2d_X509(cert, &der);
//     char *certb64 = NULL;
//     if (derlen > 0)
//     {
//         certb64 = b64_encode(der, derlen);
//         OPENSSL_free(der);
//     }
//     else
//     {
//         log_msg("i2d_X509 failed");
//         free(canon);
//         free(sigb64);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     EVP_PKEY_free(pkey);
//     X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX];
//     snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw)
//     {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n");
//     fprintf(sfw, "%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0)
//     {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }

//     free(canon);
//     free(sigb64);
//     free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* Tail the log file: open LOG_PATH, seek to current g_offset, then loop reading appended lines */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0)
//         {
//             /* file may not exist yet; sleep and retry */
//             sleep(1);
//             if (!g_run)
//                 break;
//             continue;
//         }

//         /* open log if not open */
//         if (!lf)
//         {
//             lf = fopen(LOG_PATH, "rb");
//             if (!lf)
//             {
//                 sleep(1);
//                 continue;
//             }
//             /* if g_offset is non-zero and file smaller -> file rotated or truncated -> rebuild idx */
//             if ((unsigned long long)st.st_size < g_offset)
//             {
//                 log_msg("log truncated/rotated: file size %llu < g_offset %llu; rebuilding idx", (unsigned long long)st.st_size, g_offset);
//                 trigger_alarm("Audit log truncated/rotated: rebuilding index");
//                 unsigned char new_chain[32];
//                 unsigned long long new_lines = 0, new_off = 0;
//                 if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//                 {
//                     memcpy(g_chain, new_chain, 32);
//                     g_lines = new_lines;
//                     g_offset = new_off;
//                     if (g_idx)
//                     {
//                         fclose(g_idx);
//                     }
//                     g_idx = fopen(IDX_PATH, "a+");
//                 }
//                 else
//                 {
//                     log_msg("rebuild failed");
//                     memset(g_chain, 0, 32);
//                     g_lines = 0;
//                     g_offset = 0;
//                 }
//             }
//             /* seek to offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0)
//             {
//                 /* fallback to end */
//                 fseeko(lf, 0, SEEK_END);
//             }
//         }

//         /* read lines appended */
//         while ((n = getline(&line, &cap, lf)) != -1)
//         {
//             if (n > 0)
//             {
//                 if (gmseal_on_line(line, (size_t)n) != 0)
//                 {
//                     log_msg("gmseal_on_line failed for next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                 }
//                 if (ALWAYS_REFRESH_SEAL)
//                 {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run)
//                 break;
//         }

//         if (!g_run)
//             break;

//         /* EOF: sleep then continue polling. On rotation/truncation, close and reopen to pick up new file/inode */
//         clearerr(lf);
//         /* check if file inode changed */
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0)
//         { /* can't stat -> close and retry */
//             fclose(lf);
//             lf = NULL;
//             sleep(1);
//             continue;
//         }
//         if (st2.st_ino != st.st_ino)
//         {
//             /* rotated */
//             fclose(lf);
//             lf = NULL;
//             log_msg("audit.log rotated (inode changed) - will reopen");
//             sleep(1);
//             continue;
//         }
//         /* no rotation - just sleep shortly */
//         usleep(100000); /* 100ms */
//     }

//     if (line)
//         free(line);
//     if (lf)
//         fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);
//     signal(SIGHUP, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed");
//         gm_crypto_cleanup();
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0)
//         log_msg("initial seal write failed");

//     /* main tail loop */
//     tail_and_process();

//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx)
//         fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log)
//         fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

//----------------解决.isx  .seal和audit.log行数不一致问题  版本5-------
/* ---------- gmseal-plugin.c (修正版：确保 rebuild 后重打开 g_idx) ---------- */
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>
// #include "../audit-gm/alarm.c"

// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试时可保持1，生产可改为每N行） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64)
//             BIO_free(b64);
//         if (mem)
//             BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* read PASS file (single-line password) */
// static char *read_password_file(const char *path)
// {
//     FILE *f = fopen(path, "r");
//     if (!f)
//         return NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n = getline(&line, &cap, f);
//     fclose(f);
//     if (n <= 0)
//     {
//         free(line);
//         return NULL;
//     }
//     while (n && (line[n - 1] == '\n' || line[n - 1] == '\r'))
//         line[--n] = 0;
//     return line;
// }

// /* SM3 file hex */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* trim trailing \r and \n in place; return trimmed length */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     /* do not modify buffer content except trimming length */
//     return len;
// }

// /* count lines simple */
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx from log (atomic replace). Use normalized content for lhash/chash but keep offset accounting using original n (including newline). */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf)
//     {
//         fclose(lf);
//         return -1;
//     }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         /* normalize: compute hash over content without trailing CR/LF */
//         size_t normlen = normalize_line(line, (size_t)n);

//         /* lhash = SM3(normalized_line) */
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 || EVP_DigestUpdate(c1, line, normlen) != 1 || EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         /* chash = SM3(prev_H || normalized_line) */
//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32];
//         unsigned int hm = 0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 || EVP_DigestUpdate(c2, H, sizeof(H)) != 1 || EVP_DigestUpdate(c2, line, normlen) != 1 || EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2)
//                 EVP_MD_CTX_free(c2);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         offset += (unsigned long long)n; /* offset counts original bytes including newline */
//     }

//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);

//     if (rename(tmppath, idxpath) != 0)
//     {
//         unlink(tmppath);
//         return -1;
//     }

//     /* ensure correct permissions */
//     chmod(idxpath, 0600);

//     if (out_chain)
//         memcpy(out_chain, H, 32);
//     if (out_lines)
//         *out_lines = ln;
//     if (out_offset)
//         *out_offset = offset;
//     return 0;
// }

// /* global state */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;
// static int g_need_seal = 0;

// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
//     if (sig == SIGHUP)
//         g_need_seal = 1;
// }

// /* reconstruct state from idx; if inconsistency, attempt rebuild.
//    IMPORTANT: After reading idx tail we verify that the chain computed from audit.log first last_i lines equals last_chash.
//    If not, rebuild idx from log. */
// static int reconstruct_state_from_idx(FILE *idx_fp)
// {
//     if (!idx_fp)
//         return -1;
//     rewind(idx_fp);
//     char buf[1024];
//     unsigned long long last_i = 0, last_off = 0;
//     char last_chash[65] = {0};
//     while (fgets(buf, sizeof(buf), idx_fp) != NULL)
//     {
//         unsigned long long i = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//         {
//             last_i = i;
//             last_off = off;
//             strncpy(last_chash, chex, sizeof(last_chash) - 1);
//         }
//     }

//     if (last_i == 0)
//     {
//         /* empty idx */
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* verify index tail against actual audit.log first last_i lines */
//     struct stat st;
//     if (stat(LOG_PATH, &st) == 0)
//     {
//         unsigned long long file_size = (unsigned long long)st.st_size;
//         unsigned long long file_lines = count_file_lines_simple(LOG_PATH);

//         /* if idx claims more lines or offset than actual -> rebuild */
//         if (last_off > file_size || last_i > file_lines)
//         {
//             log_msg("idx inconsistency: idx last_i=%llu last_off=%llu vs log size=%llu lines=%llu. Rebuild idx.",
//                     last_i, last_off, file_size, file_lines);
//             trigger_alarm("idx/log mismatch detected: rebuilding index from current audit.log (possible truncation or rotation)");
//             unsigned char new_chain[32];
//             unsigned long long new_lines = 0, new_off = 0;
//             if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//             {
//                 /* close old g_idx if open then reopen to point to new file */
//                 if (g_idx) { fclose(g_idx); g_idx = NULL; }
//                 g_idx = fopen(IDX_PATH, "a+");
//                 if (!g_idx)
//                 {
//                     log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//                     /* still set state with new values to keep chain consistent */
//                     memcpy(g_chain, new_chain, 32);
//                     g_lines = new_lines;
//                     g_offset = new_off;
//                     return -1;
//                 }
//                 chmod(IDX_PATH, 0600);
//                 memcpy(g_chain, new_chain, 32);
//                 g_lines = new_lines;
//                 g_offset = new_off;
//                 log_msg("rebuild idx done: lines=%llu offset=%llu", g_lines, g_offset);
//                 return 0;
//             }
//             else
//             {
//                 /* fallback to empty state */
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 return 0;
//             }
//         }

//         /* recompute chain over first last_i normalized lines and compare to last_chash */
//         unsigned char chain_bin[32];
//         unsigned long long scanned = 0;
//         if (last_i > 0)
//         {
//             /* compute chain using same logic as rebuild_idx_from_log (normalize) */
//             const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//             if (!sm3) return -1;
//             FILE *lf = fopen(LOG_PATH, "rb");
//             if (!lf) return -1;
//             char *line = NULL; size_t cap = 0; ssize_t n;
//             unsigned char H[32] = {0};
//             unsigned long long cnt = 0;
//             while (cnt < last_i && (n = getline(&line, &cap, lf)) != -1)
//             {
//                 size_t normlen = normalize_line(line, (size_t)n);
//                 EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//                 unsigned int hm = 0;
//                 unsigned char newH[32];
//                 if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 || EVP_DigestUpdate(c2, H, sizeof(H)) != 1 || EVP_DigestUpdate(c2, line, normlen) != 1 || EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//                 {
//                     if (c2) EVP_MD_CTX_free(c2);
//                     fclose(lf); free(line);
//                     return -1;
//                 }
//                 EVP_MD_CTX_free(c2);
//                 memcpy(H, newH, 32);
//                 cnt++;
//             }
//             free(line);
//             fclose(lf);
//             memcpy(chain_bin, H, 32);
//             scanned = cnt;
//         }
//         else
//         {
//             memset(chain_bin, 0, 32);
//             scanned = 0;
//         }
//         /* hex-ify */
//         char chain_hex[65]; hexdump(chain_bin, 32, chain_hex, sizeof(chain_hex));
//         if (strcmp(chain_hex, last_chash) != 0)
//         {
//             log_msg("idx tail chash doesn't match audit.log chain: idx_chash=%s recomputed=%s. Rebuild idx.", last_chash, chain_hex);
//             trigger_alarm("idx/log chash mismatch detected: rebuilding index from current audit.log");
//             unsigned char new_chain[32];
//             unsigned long long new_lines = 0, new_off = 0;
//             if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//             {
//                 /* Close old g_idx if open and reopen to point to new file */
//                 if (g_idx) { fclose(g_idx); g_idx = NULL; }
//                 g_idx = fopen(IDX_PATH, "a+");
//                 if (!g_idx)
//                 {
//                     log_msg("open %s failed after rebuild (chash mismatch path): %s", IDX_PATH, strerror(errno));
//                     /* still update internal state so plugin can continue; but return error */
//                     memcpy(g_chain, new_chain, 32);
//                     g_lines = new_lines;
//                     g_offset = new_off;
//                     return -1;
//                 }
//                 chmod(IDX_PATH, 0600);
//                 memcpy(g_chain, new_chain, 32);
//                 g_lines = new_lines;
//                 g_offset = new_off;
//                 log_msg("rebuild idx done (chash path): lines=%llu offset=%llu", g_lines, g_offset);
//                 return 0;
//             }
//             else
//             {
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 return 0;
//             }
//         }
//     }
//     else
//     {
//         log_msg("stat %s failed: %s", LOG_PATH, strerror(errno));
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         return 0;
//     }

//     /* parse last_chash into g_chain */
//     unsigned char bin[32];
//     for (int i = 0; i < 32; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&last_chash[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             memset(g_chain, 0, 32);
//             g_lines = 0;
//             g_offset = 0;
//             return -1;
//         }
//         bin[i] = (hi << 4) | lo;
//     }
//     memcpy(g_chain, bin, 32);
//     g_lines = last_i;
//     g_offset = last_off;
//     return 0;
// }

// /* initialize */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx)
//     {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_SH) != 0)
//         log_msg("flock(LOCK_SH) failed: %s", strerror(errno));
//     if (reconstruct_state_from_idx(g_idx) != 0)
//     {
//         log_msg("failed reconstruct_state_from_idx");
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     flock(fd, LOCK_UN);
//     log_msg("reconstructed state: records=%llu offset=%llu", g_lines, g_offset);
//     return 0;
// }

// /* on new line read from log file: compute lhash/chash with normalized bytes and append idx */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     /* normalize */
//     size_t normlen = normalize_line(line_buf, raw_n);

//     unsigned char lhash[32], newH[32];
//     unsigned int lml = 0, hlen = 0;

//     /* lhash = SM3(normalized_line) */
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 || EVP_DigestUpdate(ctx, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx, lhash, &lml) != 1 || lml != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     /* chash = SM3(prev_chain || normalized_line) */
//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2)
//         return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 || EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 || EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx2, newH, &hlen) != 1 || hlen != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx)
//         return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;
//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     char *secret = read_password_file(PASS_PATH);
//     if (!secret)
//     {
//         log_msg("read PASS_PATH failed");
//         return -1;
//     }

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf)
//     {
//         log_msg("open SALT_PATH failed");
//         free(secret);
//         return -1;
//     }
//     char saltbuf[256];
//     if (!fgets(saltbuf, sizeof(saltbuf), sf))
//     {
//         fclose(sf);
//         free(secret);
//         return -1;
//     }
//     fclose(sf);
//     size_t slen = strlen(saltbuf);
//     while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//         saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES * 2)
//     {
//         log_msg("salt file length unexpected");
//         free(secret);
//         return -1;
//     }
//     for (size_t i = 0; i < SALT_BYTES; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             free(secret);
//             return -1;
//         }
//         salt[i] = (hi << 4) | lo;
//     }

//     /* PBKDF2-SM3 */
//     unsigned char derived[DERIVE_BYTES];
//     const EVP_MD *md = EVP_get_digestbyname("sm3");
//     if (!md)
//     {
//         log_msg("no sm3 available");
//         free(secret);
//         return -1;
//     }
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, md, DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     char passhex[DERIVE_BYTES * 2 + 1];
//     hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret));
//     free(secret);

//     /* load p12 */
//     FILE *pf = fopen(P12_PATH, "rb");
//     if (!pf)
//     {
//         log_msg("open P12_PATH failed");
//         return -1;
//     }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf, NULL);
//     fclose(pf);
//     if (!p12)
//     {
//         log_msg("d2i_PKCS12_fp failed");
//         return -1;
//     }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//     {
//         log_msg("PKCS12_parse failed");
//         PKCS12_free(p12);
//         return -1;
//     }
//     PKCS12_free(p12);

//     /* cert fingerprint */
//     char fpr_hex[65];
//     {
//         unsigned char *der = NULL;
//         int derlen = i2d_X509(cert, &der);
//         if (derlen <= 0)
//         {
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned char mdv[EVP_MAX_MD_SIZE];
//         unsigned int mdlen = 0;
//         if (!ctx)
//         {
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             OPENSSL_free(der);
//             EVP_PKEY_free(pkey);
//             X509_free(cert);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der);
//         hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     }

//     char chain_hex[65];
//     hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0)
//     {
//         log_msg("sm3 of idx failed");
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* build canonical string */
//     char *canon = NULL;
//     if (asprintf(&canon,
//                  "version=1\n"
//                  "hash=sm3\n"
//                  "sign=sm2\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%llu\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     /* sign using gm_sm2_sign (from lib/gm_crypto) */
//     unsigned char *sig = NULL;
//     size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0)
//     {
//         log_msg("gm_sm2_sign failed");
//         free(canon);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* cert DER base64 */
//     unsigned char *der = NULL;
//     int derlen = i2d_X509(cert, &der);
//     char *certb64 = NULL;
//     if (derlen > 0)
//     {
//         certb64 = b64_encode(der, derlen);
//         OPENSSL_free(der);
//     }
//     else
//     {
//         log_msg("i2d_X509 failed");
//         free(canon);
//         free(sigb64);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     EVP_PKEY_free(pkey);
//     X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX];
//     snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw)
//     {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n");
//     fprintf(sfw, "%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0)
//     {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }

//     free(canon);
//     free(sigb64);
//     free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* Tail the log file: open LOG_PATH, seek to current g_offset, then loop reading appended lines */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0)
//         {
//             /* file may not exist yet; sleep and retry */
//             sleep(1);
//             if (!g_run)
//                 break;
//             continue;
//         }

//         /* open log if not open */
//         if (!lf)
//         {
//             lf = fopen(LOG_PATH, "rb");
//             if (!lf)
//             {
//                 sleep(1);
//                 continue;
//             }
//             /* if g_offset is non-zero and file smaller -> file rotated or truncated -> rebuild idx */
//             if ((unsigned long long)st.st_size < g_offset)
//             {
//                 log_msg("log truncated/rotated: file size %llu < g_offset %llu; rebuilding idx", (unsigned long long)st.st_size, g_offset);
//                 trigger_alarm("Audit log truncated/rotated: rebuilding index");
//                 unsigned char new_chain[32];
//                 unsigned long long new_lines = 0, new_off = 0;
//                 if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//                 {
//                     /* close old g_idx and reopen to new file */
//                     if (g_idx) { fclose(g_idx); g_idx = NULL; }
//                     g_idx = fopen(IDX_PATH, "a+");
//                     if (!g_idx)
//                     {
//                         log_msg("open %s failed after rebuild (tail path): %s", IDX_PATH, strerror(errno));
//                     }
//                     chmod(IDX_PATH, 0600);
//                     memcpy(g_chain, new_chain, 32);
//                     g_lines = new_lines;
//                     g_offset = new_off;
//                 }
//                 else
//                 {
//                     log_msg("rebuild failed");
//                     memset(g_chain, 0, 32);
//                     g_lines = 0;
//                     g_offset = 0;
//                 }
//             }
//             /* seek to offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0)
//             {
//                 /* fallback to end */
//                 fseeko(lf, 0, SEEK_END);
//             }
//         }

//         /* read lines appended */
//         while ((n = getline(&line, &cap, lf)) != -1)
//         {
//             if (n > 0)
//             {
//                 if (gmseal_on_line(line, (size_t)n) != 0)
//                 {
//                     log_msg("gmseal_on_line failed for next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                 }
//                 if (ALWAYS_REFRESH_SEAL)
//                 {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run)
//                 break;
//         }

//         if (!g_run)
//             break;

//         /* EOF: sleep then continue polling. On rotation/truncation, close and reopen to pick up new file/inode */
//         clearerr(lf);
//         /* check if file inode changed */
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0)
//         { /* can't stat -> close and retry */
//             fclose(lf);
//             lf = NULL;
//             sleep(1);
//             continue;
//         }
//         if (st2.st_ino != st.st_ino)
//         {
//             /* rotated */
//             fclose(lf);
//             lf = NULL;
//             log_msg("audit.log rotated (inode changed) - will reopen");
//             sleep(1);
//             continue;
//         }
//         /* no rotation - just sleep shortly */
//         usleep(100000); /* 100ms */
//     }

//     if (line)
//         free(line);
//     if (lf)
//         fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);
//     signal(SIGHUP, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed");
//         gm_crypto_cleanup();
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0)
//         log_msg("initial seal write failed");

//     /* main tail loop */
//     tail_and_process();

//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx)
//         fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log)
//         fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本6
//  tools/audisp-gmseal/gmseal-plugin.c
//  #define _GNU_SOURCE
//  #include <stdio.h>
//  #include <stdlib.h>
//  #include <stdarg.h>
//  #include <stdint.h>
//  #include <unistd.h>
//  #include <string.h>
//  #include <errno.h>
//  #include <signal.h>
//  #include <time.h>
//  #include <limits.h>
//  #include <sys/stat.h>
//  #include <sys/file.h>
//  #include <sys/types.h>
//  #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>
// #include "../audit-gm/alarm.c"

// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }
// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }
// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx: 提供工具函数，但本修复版不自动调用（仅在无seal时用于初始化） */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0) {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* ------------ Global state ------------ */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* signals */
// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* 解析并验证现有 seal（强制），同时验证 log 与 idx */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k=trimws(line);
//         char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b||!m) { if(b)BIO_free(b); if(m)BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64)*3/4+16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n<=0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }
// /* 计算前 N 行链 */
// static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//     {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen=0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     return 0;
// }

// /* 验证现有seal，若存在 */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present, allow init from scratch */
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }
//     /* verify signature */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }
//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     /* chain check: recompute chain on log first N lines */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain mismatch with current audit.log; possible tampering");
//             return -1;
//         }
//     }
//     else {
//         /* records=0, ok */
//     }

//     /* idx check */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal index_sm3 mismatch - idx file changed!");
//             return -1;
//         }
//         /* tail check */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65]={0};
//             while (fgets(buf, sizeof(buf), ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 trigger_alarm("Existing seal vs idx tail mismatch");
//                 return -1;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* 解析成功：读取 state，继续运行 */
//     memset(g_chain,0,32);
//     /* parse chain to g_chain */
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi,&lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i]= (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx for reading offset");
//         return -1;
//     }
//     char buf[1024]; unsigned long long last_off=0; unsigned long long last_i=0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i=i; last_off=off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx) return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0) return -1;

//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//             (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if (n<=0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n] = 0;

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) {
//         unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//         salt[i] = (hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                            EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx ||
//         EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx) EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1;
//     }

//     /* canonical */
//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     /* sign */
//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* certb64 */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len > 0) {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 初始化：不再自动重建 idx；如有 existing seal 必须严格匹配 */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     /* 打开/创建 idx 文件 */
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx) {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch with audit.log / idx. Aborting.");
//         return -1;
//     }
//     if (vs > 0) {
//         /* 没有 seal，首次启动：从 log 重构 idx（允许） */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         /* 重新打开 idx 指向新文件 */
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH, "a+");
//         if (!g_idx) {
//             log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//             return -1;
//         }
//         chmod(IDX_PATH, 0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         /* seal 验证成功的场景：已同步状态已在 verify_existing_seal 内设置了 g_chain/lines/offset */
//         log_msg("existing seal verified. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* Tail: 不再自动重建；若日志截断或 inode 变化 -> 报警退出，人工处理 */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0; ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) {
//             sleep(1);
//             if (!g_run) break;
//             continue;
//         }
//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if (!lf) { sleep(1); continue; }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 log_msg("fseeko to %llu failed, exit", g_offset);
//                 trigger_alarm("Cannot seek to last known offset in audit.log; exit");
//                 fclose(lf); lf=NULL;
//                 return -1;
//             }
//         }
//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line); line=NULL;
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run) break;
//         }
//         if (!g_run) break;

//         /* EOF：检查是否截断或 inode 改变，遇到则报警并退出（防止自动重签） */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) {
//             fclose(lf); lf=NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> exit",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; exiting to avoid auto-resign of tampered data");
//             fclose(lf); lf=NULL;
//             return -1;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> exit");
//             trigger_alarm("Audit log rotated; exiting to require manual review & re-init");
//             fclose(lf); lf=NULL;
//             return -1;
//         }
//         usleep(100000);
//     }
//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* normal exit */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本6.1(---)
/* gmseal-plugin.c  — 修改版（支持降级继续运行，定位 trusted prefix） */
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>
// #include "../audit-gm/alarm.c"

// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }
// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }
// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx: 提供工具函数，但本修复版仅在首次无seal时用于初始化 */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0) {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* ------------ Global state ------------ */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* signals */
// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* ----------------- 新增：读取 idx 到内存，用以查找 trusted prefix ----------------- */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL;
//     a->count = 0;
// }

// /* 从 idx 文件读取到内存（简单 parser），返回 0 成功 */
// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out, 0, sizeof(*out));
//     FILE *f = fopen(path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     size_t cap = 0;
//     /* 先收集条目到动态数组 */
//     IdxEntry *arr = NULL;
//     size_t cnt = 0, capv = 0;
//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii;
//             arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr;
//     out->count = cnt;
//     return 0;
// }

// /* 找到可信前缀：返回最后匹配的 idx 行数（0 表示没有匹配），并通过 out_chain/out_off 返回对应二进制 chain 与 偏移（若 matched == 0，chain 置 0，off 置 0）
//    算法：逐行读取 audit.log，在每次读到第 k 行时计算 chain，比较当前 idx 中的 chash（如果存在）。当出现第一次不匹配时（或者 idx 早已没有 k 行）则停止，trusted = k-1。
// */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         /* 无 idx 文件或无法读取 -> no trust */
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_offset) *out_offset = 0;
//         if (out_chain) memset(out_chain, 0, 32);
//         return -1;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32];
//     memset(H, 0, sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0;
//     unsigned long long last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         /* update chain: H = SM3(H || normalized_line) */
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned int mdlen = 0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         /* see if idx has an entry for this ln (idx entries might start with i=1 etc) */
//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 /* compare H (hex) with e->chash */
//                 char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     /* matched */
//                     last_good_i = e->i;
//                     last_good_off = e->off + 0; /* off field points to start offset of that line in idx semantics */
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else {
//                     /* mismatch here -> stop: trusted = ln-1 */
//                     break;
//                 }
//             } else if (e->i < ln) {
//                 /* idx has extra earlier entries we already passed - advance idx_pos until >= ln or end */
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else {
//                         break;
//                     }
//                 } else {
//                     /* no idx entry for this line -> can't trust beyond previous matched */
//                     break;
//                 }
//             } else {
//                 /* idx_pos points to an entry with i > ln (unlikely normally) -> means idx starts later -> can't trust current ln */
//                 break;
//             }
//         } else {
//             /* idx ended -> no more trust */
//             break;
//         }
//     }

//     free(line);
//     fclose(lf);

//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);

//     free_idx_array(&idx);
//     return 0;
// }

// /* ---------- 现有代码：解析 seal 并验证，但当发现链/idx不一致时改为“降级运行”（定位 trusted prefix 并继续） ---------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k=trimws(line);
//         char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b||!m) { if(b)BIO_free(b); if(m)BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64)*3/4+16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n<=0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// /* 计算前 N 行链 */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//     {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen=0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     return 0;
// }

// /* 验证现有seal，若存在。
//    行为调整：
//     - 如果没有 seal: 返回 1（first run，允许初始化）
//     - 如果 seal 存在且一致: 返回 0
//     - 如果 seal 存在但 chain/index 与文件不一致:  *不会直接失败/退出*
//           而是定位可信前缀（trusted lines）并将全局状态设置到 trusted 前缀，返回 0（继续运行，但已告警）
// */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present, allow init from scratch */
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }
//     /* verify signature */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }
//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     /* chain check: recompute chain on log first N lines */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             /* 旧行为：直接 abort。现在我们不直接 abort：
//                - 先定位 trusted prefix（用 idx 与 log 对比），并把全局状态设置到 trusted 前缀以便继续运行；
//                - 报警并记录日志，提示人工复核 */
//             log_msg("Existing seal chain mismatch with current audit.log; attempting to compute trusted prefix and continue in degraded mode");
//             trigger_alarm("Existing seal chain mismatch with audit.log; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 /* 找不到可信前缀（比如 idx 无法读取），我们降级到 0（从头开始不信任旧数据），
//                    但不要自动重建 idx（以免重新签改后的数据）。这里我们将 g_* 设为 0 并继续运行（收集新条目）。 */
//                 log_msg("find_trusted_prefix failed - no trusted prefix found; set trusted_lines=0 and continue (manual intervention required)");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 /* 设置全局状态为 trusted 前缀 */
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             /* 释放并返回正常（继续运行） */
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//     } else {
//         /* records=0, ok */
//     }

//     /* idx check */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             /* idx 被修改，报警并尝试定位可信前缀，同上 */
//             log_msg("Existing seal index_sm3 mismatch - idx file changed; attempting to find trusted prefix and continue");
//             trigger_alarm("Index file hash mismatch; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located after idx mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//         /* tail check */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65]={0};
//             while (fgets(buf, sizeof(buf), ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("Existing seal vs idx tail mismatch; try to find trusted prefix and continue");
//                 trigger_alarm("Seal vs idx tail mismatch; degraded mode (continue) - manual review required");

//                 unsigned long long trusted_lines = 0;
//                 unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//                 if (ret != 0) {
//                     log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                     memset(g_chain,0,sizeof(g_chain));
//                     g_lines = 0;
//                     g_offset = 0;
//                 } else {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Trusted prefix located after idx tail mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//                 }
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* parse chain into g_chain */
//     memset(g_chain,0,32);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi,&lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i]= (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx for reading offset");
//         return -1;
//     }
//     char buf[1024]; unsigned long long last_off=0; unsigned long long last_i=0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i=i; last_off=off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx) return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0) return -1;

//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//             (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if (n<=0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n] = 0;

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) {
//         unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//         salt[i] = (hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                            EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx ||
//         EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx) EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1;
//     }

//     /* canonical */
//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     /* sign */
//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* certb64 */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len > 0) {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 初始化：不再自动重建 idx；如有 existing seal 必须严格匹配或进入降级模式（trusted prefix） */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     /* 打开/创建 idx 文件 */
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx) {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch with audit.log / idx. Aborting.");
//         return -1;
//     }
//     if (vs > 0) {
//         /* 没有 seal，首次启动：从 log 重构 idx（允许） */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         /* 重新打开 idx 指向新文件 */
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH, "a+");
//         if (!g_idx) {
//             log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//             return -1;
//         }
//         chmod(IDX_PATH, 0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         /* seal 验证成功或已在 verify_existing_seal 中设置 trusted prefix 状态 */
//         log_msg("existing seal verified or degraded-mode prepared. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* Tail: 当检测到截断/inode 改变等问题时，进入“尝试恢复 trusted prefix 并继续”而非直接 exit */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0; ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) {
//             sleep(1);
//             if (!g_run) break;
//             continue;
//         }
//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if (!lf) { sleep(1); continue; }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 /* 不再直接 exit，而是尝试重新定位 trusted prefix */
//                 log_msg("fseeko to %llu failed: attempt to recover trusted prefix", g_offset);
//                 trigger_alarm("Cannot seek to last known offset in audit.log; attempting recovery and continuing");
//                 /* try find trusted prefix using idx and log */
//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Recovery: set trusted state lines=%llu off=%llu and reopen", g_lines, g_offset);
//                     /* reopen lf and seek again */
//                     fclose(lf); lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { log_msg("reopen log failed after recovery"); return -1; }
//                     if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                         log_msg("seek failed even after recovery; set to file end and continue");
//                         fseeko(lf, 0, SEEK_END);
//                         g_offset = (unsigned long long)ftello(lf);
//                     }
//                 } else {
//                     log_msg("Recovery attempt failed: no trusted prefix found; seeking to file end and continue");
//                     fclose(lf);
//                     lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { sleep(1); continue; }
//                     fseeko(lf, 0, SEEK_END);
//                     g_offset = (unsigned long long)ftello(lf);
//                 }
//             }
//         }

//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line); line=NULL;
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run) break;
//         }

//         if (!g_run) break;

//         /* EOF：检查是否截断或 inode 改变，遇到则报警并尝试恢复（而非直接退出） */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) {
//             fclose(lf); lf=NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> attempt recovery and continue",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; attempting recovery and continue (manual review required)");
//             /* attempt to locate trusted prefix and set g_offset appropriately */
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after truncation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 log_msg("Recovery after truncation failed: no trusted prefix; setting g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain,0,sizeof(g_chain));
//                 g_lines = 0;
//             }
//             /* reposition lf for next loop */
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> attempt recovery and continue");
//             trigger_alarm("Audit log rotated; attempting recovery and continue (manual review required)");
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after rotation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 /* fallback: start from file end */
//                 log_msg("Recovery after rotation failed: no trusted prefix; set g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//             }
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         usleep(100000);
//     }

//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* normal exit */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本6.2
/* gmseal-plugin.c  — 修改版（支持降级继续运行，定位 trusted prefix） */
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>F
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>
// #include "../audit-gm/alarm.c"

// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }
// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }
// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx: 提供工具函数，但本修复版仅在首次无seal时用于初始化 */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0) {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* ------------ Global state ------------ */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* signals */
// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* ----------------- 新增：读取 idx 到内存，用以查找 trusted prefix ----------------- */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL;
//     a->count = 0;
// }

// /* 从 idx 文件读取到内存（简单 parser），返回 0 成功 */
// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out, 0, sizeof(*out));
//     FILE *f = fopen(path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     size_t cap = 0;
//     /* 先收集条目到动态数组 */
//     IdxEntry *arr = NULL;
//     size_t cnt = 0, capv = 0;
//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii;
//             arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr;
//     out->count = cnt;
//     return 0;
// }

// /* 找到可信前缀：返回最后匹配的 idx 行数（0 表示没有匹配），并通过 out_chain/out_off 返回对应二进制 chain 与 偏移（若 matched == 0，chain 置 0，off 置 0）
//    算法：逐行读取 audit.log，在每次读到第 k 行时计算 chain，比较当前 idx 中的 chash（如果存在）。当出现第一次不匹配时（或者 idx 早已没有 k 行）则停止，trusted = k-1。
// */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         /* 无 idx 文件或无法读取 -> no trust */
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_offset) *out_offset = 0;
//         if (out_chain) memset(out_chain, 0, 32);
//         return -1;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32];
//     memset(H, 0, sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0;
//     unsigned long long last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         /* update chain: H = SM3(H || normalized_line) */
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned int mdlen = 0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         /* see if idx has an entry for this ln (idx entries might start with i=1 etc) */
//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 /* compare H (hex) with e->chash */
//                 char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     /* matched */
//                     last_good_i = e->i;
//                     last_good_off = e->off + 0; /* off field points to start offset of that line in idx semantics */
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else {
//                     /* mismatch here -> stop: trusted = ln-1 */
//                     break;
//                 }
//             } else if (e->i < ln) {
//                 /* idx has extra earlier entries we already passed - advance idx_pos until >= ln or end */
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else {
//                         break;
//                     }
//                 } else {
//                     /* no idx entry for this line -> can't trust beyond previous matched */
//                     break;
//                 }
//             } else {
//                 /* idx_pos points to an entry with i > ln (unlikely normally) -> means idx starts later -> can't trust current ln */
//                 break;
//             }
//         } else {
//             /* idx ended -> no more trust */
//             break;
//         }
//     }

//     free(line);
//     fclose(lf);

//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);

//     free_idx_array(&idx);
//     return 0;
// }

// /* ---------- 现有代码：解析 seal 并验证，但当发现链/idx不一致时改为“降级运行”（定位 trusted prefix 并继续） ---------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k=trimws(line);
//         char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b||!m) { if(b)BIO_free(b); if(m)BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64)*3/4+16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n<=0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// /* 计算前 N 行链 */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//     {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen=0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     return 0;
// }

// /* 验证现有seal，若存在。
//    行为调整：
//     - 如果没有 seal: 返回 1（first run，允许初始化）
//     - 如果 seal 存在且一致: 返回 0
//     - 如果 seal 存在但 chain/index 与文件不一致:  *不会直接失败/退出*
//           而是定位可信前缀（trusted lines）并将全局状态设置到 trusted 前缀，返回 0（继续运行，但已告警）
// */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present, allow init from scratch */
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }
//     /* verify signature */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }
//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     /* chain check: recompute chain on log first N lines */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             /* 旧行为：直接 abort。现在我们不直接 abort：
//                - 先定位 trusted prefix（用 idx 与 log 对比），并把全局状态设置到 trusted 前缀以便继续运行；
//                - 报警并记录日志，提示人工复核 */
//             log_msg("Existing seal chain mismatch with current audit.log; attempting to compute trusted prefix and continue in degraded mode");
//             trigger_alarm("Existing seal chain mismatch with audit.log; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 /* 找不到可信前缀（比如 idx 无法读取），我们降级到 0（从头开始不信任旧数据），
//                    但不要自动重建 idx（以免重新签改后的数据）。这里我们将 g_* 设为 0 并继续运行（收集新条目）。 */
//                 log_msg("find_trusted_prefix failed - no trusted prefix found; set trusted_lines=0 and continue (manual intervention required)");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 /* 设置全局状态为 trusted 前缀 */
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             /* 释放并返回正常（继续运行） */
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//     } else {
//         /* records=0, ok */
//     }

//     /* idx check */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             /* idx 被修改，报警并尝试定位可信前缀，同上 */
//             log_msg("Existing seal index_sm3 mismatch - idx file changed; attempting to find trusted prefix and continue");
//             trigger_alarm("Index file hash mismatch; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located after idx mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//         /* tail check */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65]={0};
//             while (fgets(buf, sizeof(buf), ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("Existing seal vs idx tail mismatch; try to find trusted prefix and continue");
//                 trigger_alarm("Seal vs idx tail mismatch; degraded mode (continue) - manual review required");

//                 unsigned long long trusted_lines = 0;
//                 unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//                 if (ret != 0) {
//                     log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                     memset(g_chain,0,sizeof(g_chain));
//                     g_lines = 0;
//                     g_offset = 0;
//                 } else {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Trusted prefix located after idx tail mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//                 }
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* parse chain into g_chain */
//     memset(g_chain,0,32);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi,&lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i]= (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx for reading offset");
//         return -1;
//     }
//     char buf[1024]; unsigned long long last_off=0; unsigned long long last_i=0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i=i; last_off=off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx) return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0) return -1;

//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//             (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if (n<=0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n] = 0;

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) {
//         unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//         salt[i] = (hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                            EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx ||
//         EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx) EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1;
//     }

//     /* canonical */
//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     /* sign */
//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* certb64 */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len > 0) {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 初始化：不再自动重建 idx；如有 existing seal 必须严格匹配或进入降级模式（trusted prefix） */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     /* 打开/创建 idx 文件 */
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx) {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch with audit.log / idx. Aborting.");
//         return -1;
//     }
//     if (vs > 0) {
//         /* 没有 seal，首次启动：从 log 重构 idx（允许） */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         /* 重新打开 idx 指向新文件 */
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH, "a+");
//         if (!g_idx) {
//             log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//             return -1;
//         }
//         chmod(IDX_PATH, 0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         /* seal 验证成功或已在 verify_existing_seal 中设置 trusted prefix 状态 */
//         log_msg("existing seal verified or degraded-mode prepared. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* Tail: 当检测到截断/inode 改变等问题时，进入“尝试恢复 trusted prefix 并继续”而非直接 exit */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0; ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) {
//             sleep(1);
//             if (!g_run) break;
//             continue;
//         }
//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if (!lf) { sleep(1); continue; }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 /* 不再直接 exit，而是尝试重新定位 trusted prefix */
//                 log_msg("fseeko to %llu failed: attempt to recover trusted prefix", g_offset);
//                 trigger_alarm("Cannot seek to last known offset in audit.log; attempting recovery and continuing");
//                 /* try find trusted prefix using idx and log */
//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Recovery: set trusted state lines=%llu off=%llu and reopen", g_lines, g_offset);
//                     /* reopen lf and seek again */
//                     fclose(lf); lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { log_msg("reopen log failed after recovery"); return -1; }
//                     if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                         log_msg("seek failed even after recovery; set to file end and continue");
//                         fseeko(lf, 0, SEEK_END);
//                         g_offset = (unsigned long long)ftello(lf);
//                     }
//                 } else {
//                     log_msg("Recovery attempt failed: no trusted prefix found; seeking to file end and continue");
//                     fclose(lf);
//                     lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { sleep(1); continue; }
//                     fseeko(lf, 0, SEEK_END);
//                     g_offset = (unsigned long long)ftello(lf);
//                 }
//             }
//         }

//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line); line=NULL;
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run) break;
//         }

//         if (!g_run) break;

//         /* EOF：检查是否截断或 inode 改变，遇到则报警并尝试恢复（而非直接退出） */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) {
//             fclose(lf); lf=NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> attempt recovery and continue",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; attempting recovery and continue (manual review required)");
//             /* attempt to locate trusted prefix and set g_offset appropriately */
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after truncation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 log_msg("Recovery after truncation failed: no trusted prefix; setting g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain,0,sizeof(g_chain));
//                 g_lines = 0;
//             }
//             /* reposition lf for next loop */
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> attempt recovery and continue");
//             trigger_alarm("Audit log rotated; attempting recovery and continue (manual review required)");
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after rotation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 /* fallback: start from file end */
//                 log_msg("Recovery after rotation failed: no trusted prefix; set g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//             }
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         usleep(100000);
//     }

//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* normal exit */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

/* gmseal-plugin.c
 *
 * gm based audit sealing plugin (improved recovery + tamper reporting)
 *
 * Place next to your other audit-gm sources.
 *
 * Build: gcc -Wall -O2 -o audisp-gmseal gmseal-plugin.c -lcrypto  (plus link gm_crypto etc)
 *
 */
// 版本7
//  #define _GNU_SOURCE
//  #include <stdio.h>
//  #include <stdlib.h>
//  #include <stdarg.h>
//  #include <stdint.h>
//  #include <unistd.h>
//  #include <string.h>
//  #include <errno.h>
//  #include <signal.h>
//  #include <time.h>
//  #include <limits.h>
//  #include <sys/stat.h>
//  #include <sys/file.h>
//  #include <sys/types.h>
//  #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"
// #define TAMPER_PATH "/var/log/audit/audit.log.tamper"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// #define ALWAYS_REFRESH_SEAL 1

// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64)
//             BIO_free(b64);
//         if (mem)
//             BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m)
//     {
//         if (b)
//             BIO_free(b);
//         if (m)
//             BIO_free(m);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0)
//     {
//         free(buf);
//         return NULL;
//     }
//     *olen = (size_t)n;
//     return buf;
// }

// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }

// /* sm3 helpers (using openssl EVP) */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//     {
//         fclose(f);
//         return -1;
//     }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//     {
//         EVP_MD_CTX_free(ctx);
//         fclose(f);
//         return -1;
//     }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//     {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1)
//         {
//             EVP_MD_CTX_free(ctx);
//             fclose(f);
//             return -1;
//         }
//     }
//     fclose(f);
//     unsigned char md[32];
//     unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* count lines quickly */
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f)
//         return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx from log (full rebuild) */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     unsigned long long offset = 0;
//     unsigned char H[32];
//     memset(H, 0, 32);

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf)
//     {
//         fclose(lf);
//         return -1;
//     }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         /* lhash */
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32];
//         unsigned int lm = 0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 || EVP_DigestUpdate(c1, line, normlen) != 1 || EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1)
//                 EVP_MD_CTX_free(c1);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         /* chain update */
//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32];
//         unsigned int hm = 0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 || EVP_DigestUpdate(c2, H, sizeof(H)) != 1 || EVP_DigestUpdate(c2, line, normlen) != 1 || EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2)
//                 EVP_MD_CTX_free(c2);
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf);
//             fclose(tf);
//             unlink(tmppath);
//             free(line);
//             return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0)
//     {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain)
//         memcpy(out_chain, H, 32);
//     if (out_lines)
//         *out_lines = ln;
//     if (out_offset)
//         *out_offset = offset;
//     return 0;
// }

// /* parse seal file (same as earlier) */
// struct seal
// {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s)
// {
//     size_t n = strlen(s);
//     while (n && (s[n - 1] == '\n' || s[n - 1] == '\r'))
//         s[--n] = 0;
// }
// static char *trimws(char *s)
// {
//     while (*s == ' ' || *s == '\t')
//         s++;
//     size_t n = strlen(s);
//     while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || s[n - 1] == '\n'))
//         s[--n] = 0;
//     return s;
// }

// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f)
//         return -1;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1)
//     {
//         if (line[0] == '#' || line[0] == 0)
//             continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq)
//             continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq + 1);
//         if (strcmp(k, "version") == 0)
//             S->version = strdup(v);
//         else if (strcmp(k, "hash") == 0)
//             S->hash = strdup(v);
//         else if (strcmp(k, "sign") == 0)
//             S->sign = strdup(v);
//         else if (strcmp(k, "sm2_id") == 0)
//             S->sm2_id = strdup(v);
//         else if (strcmp(k, "path") == 0)
//             S->path = strdup(v);
//         else if (strcmp(k, "records") == 0)
//             S->records = strdup(v);
//         else if (strcmp(k, "chain") == 0)
//             S->chain = strdup(v);
//         else if (strcmp(k, "key_fpr") == 0)
//             S->key_fpr = strdup(v);
//         else if (strcmp(k, "cert_der_base64") == 0)
//             S->cert_b64 = strdup(v);
//         else if (strcmp(k, "sig_base64") == 0)
//             S->sig_b64 = strdup(v);
//         else if (strcmp(k, "index_sm3") == 0)
//             S->index_sm3 = strdup(v);
//     }
//     free(line);
//     fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//         return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version);
//     free(S->hash);
//     free(S->sign);
//     free(S->sm2_id);
//     free(S->path);
//     free(S->records);
//     free(S->chain);
//     free(S->key_fpr);
//     free(S->index_sm3);
//     free(S->cert_b64);
//     free(S->sig_b64);
// }

// /* global state */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* Utility: read idx into memory (simple) */
// typedef struct
// {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } idx_entry_t;

// static idx_entry_t *read_idx_all(const char *idxpath, size_t *out_count)
// {
//     *out_count = 0;
//     FILE *f = fopen(idxpath, "r");
//     if (!f)
//         return NULL;
//     idx_entry_t *arr = NULL;
//     size_t cap = 0;
//     char buf[1024];
//     while (fgets(buf, sizeof(buf), f))
//     {
//         if (buf[0] == '#')
//             continue;
//         unsigned long long i = 0, off = 0;
//         char l[128] = {0}, c[128] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, l, c) == 4)
//         {
//             if (*out_count >= cap)
//             {
//                 size_t nc = cap ? cap * 2 : 1024;
//                 idx_entry_t *p = realloc(arr, nc * sizeof(idx_entry_t));
//                 if (!p)
//                 {
//                     free(arr);
//                     fclose(f);
//                     return NULL;
//                 }
//                 arr = p;
//                 cap = nc;
//             }
//             idx_entry_t *e = &arr[*out_count];
//             e->i = i;
//             e->off = off;
//             strncpy(e->lhash, l, sizeof(e->lhash) - 1);
//             e->lhash[64] = 0;
//             strncpy(e->chash, c, sizeof(e->chash) - 1);
//             e->chash[64] = 0;
//             (*out_count)++;
//         }
//     }
//     fclose(f);
//     return arr;
// }

// /* Compare old idx vs newly rebuilt idx: produce tampered lines list */
// static int compare_oldidx_newidx_and_report(const char *old_idx_path, const char *new_idx_path, const char *tamper_path)
// {
//     size_t oldc = 0, newc = 0;
//     idx_entry_t *old = read_idx_all(old_idx_path, &oldc);
//     idx_entry_t *nw = read_idx_all(new_idx_path, &newc);
//     if (!old || !nw)
//     {
//         if (old)
//             free(old);
//         if (nw)
//             free(nw);
//         return -1;
//     }

//     /* we'll mark lines where lhash differs OR where idx entries missing
//        For convenience, consider up to min(oldc, newc) lines.
//     */
//     FILE *tf = fopen(tamper_path, "w");
//     if (!tf)
//     {
//         free(old);
//         free(nw);
//         return -1;
//     }
//     int reported = 0;
//     size_t minc = oldc < newc ? oldc : newc;
//     for (size_t k = 0; k < minc; k++)
//     {
//         if (strcmp(old[k].lhash, nw[k].lhash) != 0)
//         {
//             fprintf(tf, "%llu\n", (unsigned long long)old[k].i);
//             reported++;
//         }
//     }
//     /* if old had more lines than new: those lines were removed (truncated) */
//     if (oldc > newc)
//     {
//         for (size_t k = newc; k < oldc; k++)
//         {
//             fprintf(tf, "%llu\n", (unsigned long long)old[k].i);
//             reported++;
//         }
//     }
//     else if (newc > oldc)
//     {
//         /* new lines appended are new and not in old; not necessarily tampered */
//         /* nothing to report by default */
//     }
//     fclose(tf);
//     free(old);
//     free(nw);
//     return reported;
// }

// /* compute sm3 chain for first N lines */
// static int sm3_chain_file_limit(const char *path,
//                                 unsigned long long limit_lines,
//                                 unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;

//     unsigned char H[32];
//     memset(H, 0, 32);
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path, "rb");
//     if (!fp)
//         return -1;

//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     while ((limit_lines == 0 || scanned < limit_lines) &&
//            (n = getline(&line, &cap, fp)) != -1)
//     {
//         size_t normlen = n;
//         while (normlen > 0 &&
//                (line[normlen - 1] == '\n' || line[normlen - 1] == '\r'))
//             normlen--;

//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) {
//             free(line);
//             fclose(fp);
//             return -1;
//         }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, NULL) != 1) {
//             EVP_MD_CTX_free(ctx);
//             free(line);
//             fclose(fp);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);

//     memcpy(out32, H, 32);
//     return 0;
// }

// /* Verify existing seal but if mismatch -> attempt recovery (rebuild idx) and report tamper */
// static int verify_existing_seal_or_rebuild(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0)
//     {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present */
//     }

//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0)
//     {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0 || strcmp(S.sm2_id, SM2_ID) != 0)
//     {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }

//     /* verify signature & cert fingerprint as before */
//     size_t cert_len = 0;
//     unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der)
//     {
//         free_seal(&S);
//         trigger_alarm("Existing seal cert b64 decode failed");
//         return -1;
//     }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert)
//     {
//         free_seal(&S);
//         trigger_alarm("Existing seal d2i_X509 failed");
//         return -1;
//     }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub)
//     {
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("Existing seal get pubkey failed");
//         return -1;
//     }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2)
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }
//     /* fingerprint */
//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0)
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("i2d_X509 failed");
//         return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE];
//     unsigned int mdlen = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der2, der2len) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx)
//             EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der2);
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der2);
//     char fpr_hex[65];
//     hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0)
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }

//     /* verify signature */
//     char *canon = NULL;
//     if (asprintf(&canon,
//                  "version=%s\n"
//                  "hash=%s\n"
//                  "sign=%s\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%s\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  S.version, S.hash, S.sign, S.sm2_id, S.path, S.records, S.chain, S.key_fpr, S.index_sm3) < 0)
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("asprintf failed");
//         return -1;
//     }
//     size_t siglen = 0;
//     unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig)
//     {
//         free(canon);
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("sig decode failed");
//         return -1;
//     }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig);
//     free(canon);
//     if (v != 1)
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("Existing seal signature invalid");
//         return -1;
//     }

//     /* chain check: recompute chain on log first N lines */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0)
//     {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit(S.path, N, chainR) != 0)
//         {
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65];
//         hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0)
//         {
//             /* --- MISMATCH: instead of abort, we try recovery by rebuilding idx from current log
//              * and produce tamper report comparing old idx -> new idx
//              */
//             log_msg("Existing seal chain mismatch with current audit.log; attempt auto-rebuild idx and report tamper");
//             trigger_alarm("Existing seal chain mismatch - attempting auto-rebuild & tamper report");

//             /* back up old idx if exists */
//             if (access(IDX_PATH, R_OK) == 0)
//             {
//                 char bak[PATH_MAX];
//                 snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, time(NULL));
//                 if (rename(IDX_PATH, bak) == 0)
//                 {
//                     log_msg("old idx backed up to %s", bak);
//                     /* try to rebuild idx from log */
//                     unsigned char new_chain[32];
//                     unsigned long long new_lines = 0, new_off = 0;
//                     if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//                     {
//                         log_msg("rebuild_idx_from_log succeeded: lines=%llu off=%llu", new_lines, new_off);
//                         /* compare old & new idx, write tamper file */
//                         int reported = compare_oldidx_newidx_and_report(bak, IDX_PATH, TAMPER_PATH);
//                         if (reported < 0)
//                         {
//                             log_msg("compare old/new idx failed");
//                         }
//                         else
//                         {
//                             log_msg("tamper report written: %s (reported %d lines)", TAMPER_PATH, reported);
//                         }
//                         /* free resources and proceed treating as first-run (we will init state below) */
//                         free_seal(&S);
//                         EVP_PKEY_free(pub);
//                         X509_free(cert);
//                         return 1; /* signal first-run style: rebuilt idx present */
//                     }
//                     else
//                     {
//                         log_msg("rebuild_idx_from_log failed during recovery");
//                         /* restore old idx if needed */
//                         rename(bak, IDX_PATH);
//                     }
//                 }
//                 else
//                 {
//                     log_msg("failed to back up old idx file for recovery");
//                 }
//             }
//             else
//             {
//                 log_msg("no old idx to back up - cannot do compare");
//             }

//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             return -1;
//         }
//     }

//     /* idx check: verify idx sm3 */
//     if (access(IDX_PATH, R_OK) == 0)
//     {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0)
//         {
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0)
//         {
//             /* index mismatch: try rebuild & tamper report */
//             log_msg("Existing seal index_sm3 mismatch - attempt auto-rebuild idx & report tamper");
//             trigger_alarm("Existing seal index_sm3 mismatch - attempting auto-rebuild & tamper report");

//             char bak[PATH_MAX];
//             snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, time(NULL));
//             if (rename(IDX_PATH, bak) == 0)
//             {
//                 unsigned char new_chain[32];
//                 unsigned long long new_lines = 0, new_off = 0;
//                 if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//                 {
//                     log_msg("rebuild idx succeeded after index_sm3 mismatch");
//                     int reported = compare_oldidx_newidx_and_report(bak, IDX_PATH, TAMPER_PATH);
//                     if (reported >= 0)
//                         log_msg("tamper report written: %s (reported=%d)", TAMPER_PATH, reported);
//                     free_seal(&S);
//                     EVP_PKEY_free(pub);
//                     X509_free(cert);
//                     return 1;
//                 }
//                 else
//                 {
//                     log_msg("rebuild idx failed during index_sm3 mismatch recovery");
//                     rename(bak, IDX_PATH);
//                 }
//             }
//             else
//             {
//                 log_msg("cannot backup idx for rebuild");
//             }
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             return -1;
//         }

//         /* tail check: idx last chash must match seal chain */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix)
//         {
//             char buf[1024];
//             unsigned long long last_i = 0;
//             char last_ch[65] = {0};
//             while (fgets(buf, sizeof(buf), ix))
//             {
//                 if (buf[0] == '#')
//                     continue;
//                 unsigned long long i = 0, off = 0;
//                 char l[128] = {0}, c[128] = {0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, l, c) == 4)
//                 {
//                     last_i = i;
//                     strncpy(last_ch, c, sizeof(last_ch) - 1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0)
//             {
//                 log_msg("Existing seal vs idx tail mismatch - attempt rebuild & tamper report");
//                 trigger_alarm("Existing seal vs idx tail mismatch - attempting auto-rebuild & report");
//                 char bak[PATH_MAX];
//                 snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, time(NULL));
//                 if (rename(IDX_PATH, bak) == 0)
//                 {
//                     unsigned char new_chain[32];
//                     unsigned long long new_lines = 0, new_off = 0;
//                     if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) == 0)
//                     {
//                         log_msg("rebuild idx succeeded after tail mismatch");
//                         int reported = compare_oldidx_newidx_and_report(bak, IDX_PATH, TAMPER_PATH);
//                         if (reported >= 0)
//                             log_msg("tamper report written: %s (reported=%d)", TAMPER_PATH, reported);
//                         free_seal(&S);
//                         EVP_PKEY_free(pub);
//                         X509_free(cert);
//                         return 1;
//                     }
//                     else
//                     {
//                         log_msg("rebuild idx failed for tail mismatch");
//                         rename(bak, IDX_PATH);
//                     }
//                 }
//                 else
//                 {
//                     log_msg("cannot backup idx for rebuild");
//                 }
//                 EVP_PKEY_free(pub);
//                 X509_free(cert);
//                 free_seal(&S);
//                 return -1;
//             }
//         }
//         else
//         {
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     }
//     else
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* parse chain to g_chain and set g_lines/g_offset from idx */
//     memset(g_chain, 0, 32);
//     for (int i = 0; i < 32; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&S.chain[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i] = (hi << 4) | lo;
//     }
//     g_lines = strtoull(S.records, NULL, 10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH, "r");
//     if (!ix)
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("Cannot open idx");
//         return -1;
//     }
//     char buf[1024];
//     unsigned long long last_off = 0;
//     unsigned long long last_i = 0;
//     while (fgets(buf, sizeof(buf), ix))
//     {
//         if (buf[0] == '#')
//             continue;
//         unsigned long long i = 0, off = 0;
//         char lhex[128] = {0}, chex[128] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//         {
//             last_i = i;
//             last_off = off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines)
//     {
//         EVP_PKEY_free(pub);
//         X509_free(cert);
//         free_seal(&S);
//         trigger_alarm("idx last_i != seal records");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub);
//     X509_free(cert);
//     free_seal(&S);
//     return 0;
// }

// /* gmseal_on_line: append idx entry and update chain */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3)
//         return -1;
//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm = 0, hm = 0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx)
//         return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 || EVP_DigestUpdate(ctx, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2)
//         return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 || EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 || EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 || EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx)
//         return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0)
//         return -1;

//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf)
//     {
//         log_msg("open PASS_PATH failed");
//         return -1;
//     }
//     char *secret = NULL;
//     size_t cap = 0;
//     ssize_t n = getline(&secret, &cap, pf);
//     fclose(pf);
//     if (n <= 0)
//     {
//         free(secret);
//         log_msg("read PASS_PATH empty");
//         return -1;
//     }
//     while (n && (secret[n - 1] == '\n' || secret[n - 1] == '\r'))
//         secret[--n] = 0;

//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf)
//     {
//         log_msg("open SALT_PATH failed");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     char saltbuf[256];
//     if (!fgets(saltbuf, sizeof(saltbuf), sf))
//     {
//         fclose(sf);
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     fclose(sf);
//     size_t slen = strlen(saltbuf);
//     while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//         saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES * 2)
//     {
//         log_msg("salt length error");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     for (size_t i = 0; i < SALT_BYTES; i++)
//     {
//         unsigned int hi, lo;
//         if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//         {
//             OPENSSL_cleanse(secret, strlen(secret));
//             free(secret);
//             return -1;
//         }
//         salt[i] = (hi << 4) | lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret));
//         free(secret);
//         return -1;
//     }
//     char passhex[DERIVE_BYTES * 2 + 1];
//     hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret));
//     free(secret);

//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2)
//     {
//         log_msg("open P12_PATH failed");
//         return -1;
//     }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12)
//     {
//         log_msg("d2i_PKCS12_fp failed");
//         return -1;
//     }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//     {
//         log_msg("PKCS12_parse failed");
//         PKCS12_free(p12);
//         return -1;
//     }
//     PKCS12_free(p12);

//     unsigned char *der = NULL;
//     int derlen = i2d_X509(cert, &der);
//     if (derlen <= 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE];
//     unsigned int mdlen = 0;
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx)
//             EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65];
//     hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65];
//     hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         log_msg("sm3 idx failed");
//         return -1;
//     }

//     char *canon = NULL;
//     if (asprintf(&canon,
//                  "version=1\n"
//                  "hash=sm3\n"
//                  "sign=sm2\n"
//                  "sm2_id=%s\n"
//                  "path=%s\n"
//                  "records=%llu\n"
//                  "chain=%s\n"
//                  "key_fpr=%s\n"
//                  "index_sm3=%s\n",
//                  SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     unsigned char *sig = NULL;
//     size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0)
//     {
//         log_msg("gm_sm2_sign failed");
//         free(canon);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     unsigned char *der2 = NULL;
//     int der2len = i2d_X509(cert, &der2);
//     char *certb64 = NULL;
//     if (der2len > 0)
//     {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     }
//     else
//     {
//         log_msg("i2d_X509 failed");
//         free(canon);
//         free(sigb64);
//         EVP_PKEY_free(pkey);
//         X509_free(cert);
//         return -1;
//     }

//     EVP_PKEY_free(pkey);
//     X509_free(cert);

//     char tmp[PATH_MAX];
//     snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw)
//     {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0)
//     {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon);
//         free(sigb64);
//         free(certb64);
//         return -1;
//     }
//     free(canon);
//     free(sigb64);
//     free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// static int gmseal_init(void)
// {
//     memset(g_chain, 0, 32);
//     g_lines = 0;
//     g_offset = 0;
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx)
//     {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int vs = verify_existing_seal_or_rebuild();
//     if (vs < 0)
//     {
//         log_msg("Existing seal mismatch with audit.log / idx and recovery failed. Aborting.");
//         return -1;
//     }
//     if (vs > 0)
//     {
//         /* We rebuilt idx from log in recovery path: now load g_chain/lines/offset from idx file */
//         /* read idx tail */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (!ix)
//         {
//             log_msg("open %s failed after rebuild", IDX_PATH);
//             return -1;
//         }
//         char buf[1024];
//         unsigned long long last_off = 0;
//         unsigned long long last_i = 0;
//         char last_ch[65] = {0};
//         while (fgets(buf, sizeof(buf), ix))
//         {
//             if (buf[0] == '#')
//                 continue;
//             unsigned long long i = 0, off = 0;
//             char lhex[128] = {0}, chex[128] = {0};
//             if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//             {
//                 last_i = i;
//                 last_off = off;
//                 strncpy(last_ch, chex, sizeof(last_ch) - 1);
//             }
//         }
//         fclose(ix);
//         g_lines = last_i;
//         g_offset = last_off;
//         memset(g_chain, 0, 32);
//         for (int i = 0; i < 32; i++)
//         {
//             unsigned int hi, lo;
//             if (sscanf(last_ch + i * 2, "%1x%1x", &hi, &lo) == 2)
//                 g_chain[i] = (hi << 4) | lo;
//             else
//             {
//                 memset(g_chain, 0, 32);
//                 break;
//             }
//         }
//         log_msg("rebuild init: lines=%llu off=%llu", g_lines, g_offset);
//         return 0;
//     }
//     else
//     {
//         log_msg("existing seal verified. state lines=%llu off=%llu", g_lines, g_offset);
//         return 0;
//     }
// }

// /* Recovery when truncated while tailing: rebuild idx and continue */
// static int recover_after_truncate(void)
// {
//     log_msg("Recovery after truncation: attempt rebuild idx from current audit.log");
//     trigger_alarm("Audit log truncated: attempting rebuild idx & continue");
//     /* backup old idx */
//     if (access(IDX_PATH, R_OK) == 0)
//     {
//         char bak[PATH_MAX];
//         snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, time(NULL));
//         rename(IDX_PATH, bak);
//     }
//     unsigned char new_chain[32];
//     unsigned long long new_lines = 0, new_off = 0;
//     if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0)
//     {
//         log_msg("rebuild_idx_from_log failed during truncate recovery");
//         return -1;
//     }
//     /* compare with backup if exists */
//     /* (we attempt to locate previous backup with suffix .bak.<ts> — simple approach: search for any .bak file) */
//     /* For simplicity: don't find previous backup name here; assume tamper file is already written if needed */
//     /* Update state */
//     if (g_idx)
//     {
//         fclose(g_idx);
//         g_idx = NULL;
//     }
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx)
//     {
//         log_msg("open idx after rebuild failed");
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);
//     memcpy(g_chain, new_chain, 32);
//     g_lines = new_lines;
//     g_offset = new_off;
//     log_msg("Recovery after truncation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//     return 0;
// }

// /* main tail loop (attempt recovery when possible) */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0)
//         {
//             sleep(1);
//             if (!g_run)
//                 break;
//             continue;
//         }
//         if (!lf)
//         {
//             lf = fopen(LOG_PATH, "rb");
//             if (!lf)
//             {
//                 sleep(1);
//                 continue;
//             }
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0)
//             {
//                 log_msg("fseeko to %llu failed, attempt recovery", g_offset);
//                 if (recover_after_truncate() != 0)
//                 {
//                     trigger_alarm("Cannot seek to last known offset in audit.log; abort");
//                     fclose(lf);
//                     lf = NULL;
//                     return -1;
//                 }
//                 /* reopen file after recovery */
//                 if (lf)
//                 {
//                     fclose(lf);
//                     lf = NULL;
//                 }
//                 lf = fopen(LOG_PATH, "rb");
//                 if (!lf)
//                 {
//                     sleep(1);
//                     continue;
//                 }
//                 if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0)
//                 {
//                     log_msg("fseeko still failed after recovery; abort");
//                     trigger_alarm("seek failed after recovery; abort");
//                     fclose(lf);
//                     lf = NULL;
//                     return -1;
//                 }
//             }
//         }
//         while ((n = getline(&line, &cap, lf)) != -1)
//         {
//             if (n > 0)
//             {
//                 if (gmseal_on_line(line, (size_t)n) != 0)
//                 {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line);
//                     line = NULL;
//                     fclose(lf);
//                     lf = NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL)
//                 {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run)
//                 break;
//         }
//         if (!g_run)
//             break;

//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0)
//         {
//             fclose(lf);
//             lf = NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset)
//         {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> attempt recovery", (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; attempting recovery to avoid auto-resign of tampered data");
//             fclose(lf);
//             lf = NULL;
//             if (recover_after_truncate() != 0)
//             {
//                 log_msg("recover_after_truncate failed - aborting");
//                 return -1;
//             }
//             continue;
//         }
//         if (st2.st_ino != st.st_ino)
//         {
//             log_msg("audit.log inode changed (rotation) -> attempt recovery by reopen & rebuild idx if needed");
//             trigger_alarm("Audit log rotated; attempting to recover & continue");
//             /* On rotation, we will close and reopen, but we must ensure we do not auto-sign old truncated content.
//              * Simplest approach: close and reopen; fseeko to g_offset if possible, else rebuild.
//              */
//             fclose(lf);
//             lf = NULL;
//             /* reopen handled in next loop iteration */
//             continue;
//         }
//         usleep(100000);
//     }
//     if (line)
//         free(line);
//     if (lf)
//         fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log)
//             fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0)
//     {
//         log_msg("initial seal write failed");
//     }

//     if (tail_and_process() != 0)
//     {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx)
//         fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log)
//         fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本8
/* gmseal-plugin.c
 *
 * audisp 插件：维护 audit.log.idx 和 audit.log.seal（SM3+SM2）
 *
 * 设计原则:
 *  - 首次运行（无 seal）可以从 audit.log 重建 idx 并签名（初始化）。
 *  - 如果已存在 seal，但校验不通过（chain/index mismatch 等），
 *    则不要自动替换 idx 或自动重新签名。这样避免掩盖篡改证据。
 *    插件会备份 idx（.bak.<ts>）、生成 tamper 报告 /var/log/audit/audit.log.tamper，
 *    记录日志并退出。由管理员进行人工核查/恢复。
 *  - 在 tail 过程中若发现截断/rotation/chain mismatch，插件也会报警并退出。
 */

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"
// #define TAMPER_PATH "/var/log/audit/audit.log.tamper"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }
// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }
// static unsigned char *b64_decode(const char *b64, size_t *olen) {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b||!m) { if(b)BIO_free(b); if(m)BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b,m);
//     size_t cap = strlen(b64)*3/4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// static size_t normalize_line(char *buf, size_t len) {
//     while (len>0 && (buf[len-1]=='\n' || buf[len-1]=='\r')) len--;
//     return len;
// }

// /* sm3 helpers (using openssl EVP) */
// static int sm3_file_hex(const char *path, char out_hex[65]) {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path,"rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n=fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen=0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* rebuild idx: 提供工具函数（仅在首次无 seal 时自动调用或人工触发） */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0) {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* ------------ Global state ------------ */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* signals */
// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* 解析并验证现有 seal（严格） */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k=trimws(line);
//         char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S) {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }

// /* read idx whole (simple) */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } idx_entry_t;
// static idx_entry_t *read_idx_all(const char *idxpath, size_t *out_count) {
//     *out_count = 0;
//     FILE *f = fopen(idxpath,"r");
//     if (!f) return NULL;
//     idx_entry_t *arr = NULL; size_t cap = 0;
//     char buf[1024];
//     while (fgets(buf,sizeof(buf),f)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0;
//         char l[128]={0}, c[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, l, c) == 4) {
//             if (*out_count >= cap) {
//                 size_t nc = cap ? cap*2 : 1024;
//                 idx_entry_t *p = realloc(arr, nc * sizeof(idx_entry_t));
//                 if (!p) { free(arr); fclose(f); return NULL; }
//                 arr = p; cap = nc;
//             }
//             idx_entry_t *e = &arr[*out_count];
//             e->i = i; e->off = off;
//             strncpy(e->lhash, l, sizeof(e->lhash)-1); e->lhash[64]=0;
//             strncpy(e->chash, c, sizeof(e->chash)-1); e->chash[64]=0;
//             (*out_count)++;
//         }
//     }
//     fclose(f);
//     return arr;
// }

// /* compare old idx vs new idx and write tamper file with suspicious line numbers */
// static int compare_oldidx_newidx_and_report(const char *old_idx_path, const char *new_idx_path, const char *tamper_path) {
//     size_t oldc=0, newc=0;
//     idx_entry_t *old = read_idx_all(old_idx_path, &oldc);
//     idx_entry_t *nw  = read_idx_all(new_idx_path, &newc);
//     if (!old || !nw) {
//         if (old) free(old);
//         if (nw) free(nw);
//         return -1;
//     }

//     FILE *tf = fopen(tamper_path, "w");
//     if (!tf) { free(old); free(nw); return -1; }
//     int reported = 0;
//     size_t minc = oldc < newc ? oldc : newc;
//     for (size_t k=0;k<minc;k++){
//         if (strcmp(old[k].lhash, nw[k].lhash) != 0) {
//             fprintf(tf, "%llu\n", (unsigned long long)old[k].i);
//             reported++;
//         }
//     }
//     if (oldc > newc) {
//         for (size_t k=newc;k<oldc;k++){
//             fprintf(tf, "%llu\n", (unsigned long long)old[k].i);
//             reported++;
//         }
//     }
//     fclose(tf);
//     free(old); free(nw);
//     return reported;
// }

// /* Verify existing seal:
//  *  - if no seal -> return 1 (first-run allowed)
//  *  - if seal OK -> initialize g_chain/g_lines/g_offset and return 0
//  *  - if seal exists but mismatch -> do NOT auto-rebuild and replace: write tamper report and return -1
//  *    (But we attempt to create a report by building a temporary new idx and comparing)
//  */

//  static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32];
//     memset(H, 0, sizeof(H));
//     unsigned long long scanned = 0;

//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((limit_lines == 0 || scanned < limit_lines) && (n = getline(&line, &cap, fp)) != -1) {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         unsigned int mdlen = 0;
//         if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     if (out_scanned) *out_scanned = scanned;
//     return 0;
// }

// static int verify_existing_seal_or_rebuild(void) {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present */
//     }

//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }

//     /* verify signature */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }
//     /* verify signature */
//     char *canon = NULL;
//     if (asprintf(&canon,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S.version, S.hash, S.sign, S.sm2_id, S.path, S.records, S.chain, S.key_fpr, S.index_sm3) < 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     /* chain check */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         unsigned long long scanned = 0;
//         if (sm3_chain_file_limit(S.path, N, chainR, &scanned) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             /* mismatch: do not auto-overwrite. Instead: attempt to generate a *temporary* new idx
//              * (write to IDX_PATH+".tmp") and compare with existing idx to produce tamper report.
//              * Then backup existing idx (.bak.<ts>) and write tamper file, but DO NOT replace idx automatically.
//              */
//             log_msg("Existing seal chain mismatch with current audit.log; will not auto-rebuild. Creating tamper report and exiting.");
//             trigger_alarm("Existing seal chain mismatch - manual review required");

//             /* build a temporary idx from log for comparison */
//             char tmpidx[PATH_MAX]; snprintf(tmpidx,sizeof(tmpidx), "%s.tmp.%ld", IDX_PATH, (long)time(NULL));
//             unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//             int rb = rebuild_idx_from_log(LOG_PATH, tmpidx, new_chain, &new_lines, &new_off);
//             if (rb == 0) {
//                 /* compare old idx (if exists) */
//                 if (access(IDX_PATH, R_OK) == 0) {
//                     char bak[PATH_MAX]; snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, (long)time(NULL));
//                     if (rename(IDX_PATH, bak) == 0) {
//                         log_msg("backed up idx to %s for tamper analysis", bak);
//                         int reported = compare_oldidx_newidx_and_report(bak, tmpidx, TAMPER_PATH);
//                         if (reported >= 0) log_msg("Tamper report written: %s (reported=%d)", TAMPER_PATH, reported);
//                         /* restore old idx (we don't overwrite) */
//                         rename(bak, IDX_PATH);
//                     } else {
//                         log_msg("failed to backup idx for tamper analysis");
//                     }
//                 } else {
//                     /* no old idx to compare, but we still write tmp idx for admin */
//                     log_msg("no existing idx to compare; tmp idx generated: %s", tmpidx);
//                     /* keep tmp idx for admin to inspect */
//                 }
//             } else {
//                 log_msg("failed to create tmp idx for tamper analysis");
//             }
//             /* cleanup tmp idx if exists? leave it for admin to inspect */
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return -1;
//         }
//     }

//     /* idx check: index_sm3 must match */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             log_msg("Existing seal index_sm3 mismatch - will not auto-rebuild. Writing tamper report and exiting.");
//             trigger_alarm("Existing seal index_sm3 mismatch - manual review required");

//             /* attempt temporary rebuild for reporting */
//             char tmpidx[PATH_MAX]; snprintf(tmpidx,sizeof(tmpidx), "%s.tmp.%ld", IDX_PATH, (long)time(NULL));
//             unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//             if (rebuild_idx_from_log(LOG_PATH, tmpidx, new_chain, &new_lines, &new_off) == 0) {
//                 int reported = 0;
//                 char bak[PATH_MAX]; snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, (long)time(NULL));
//                 if (rename(IDX_PATH, bak) == 0) {
//                     reported = compare_oldidx_newidx_and_report(bak, tmpidx, TAMPER_PATH);
//                     rename(bak, IDX_PATH);
//                 } else {
//                     reported = compare_oldidx_newidx_and_report(IDX_PATH, tmpidx, TAMPER_PATH);
//                 }
//                 if (reported >= 0) log_msg("tamper report written: %s (reported=%d)", TAMPER_PATH, reported);
//             } else {
//                 log_msg("failed to rebuild tmp idx for index_sm3 mismatch");
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return -1;
//         }

//         /* tail check */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65]={0};
//             while (fgets(buf, sizeof(buf), ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != (unsigned long long)strtoull(S.records, NULL, 10) || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("Existing seal vs idx tail mismatch - will not auto-rebuild. Writing tamper report and exiting.");
//                 trigger_alarm("Existing seal vs idx tail mismatch - manual review required");

//                 /* attempt reporting via tmp rebuild */
//                 char tmpidx[PATH_MAX]; snprintf(tmpidx,sizeof(tmpidx), "%s.tmp.%ld", IDX_PATH, (long)time(NULL));
//                 unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//                 if (rebuild_idx_from_log(LOG_PATH, tmpidx, new_chain, &new_lines, &new_off) == 0) {
//                     int reported = 0;
//                     char bak[PATH_MAX]; snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, (long)time(NULL));
//                     if (rename(IDX_PATH, bak) == 0) {
//                         reported = compare_oldidx_newidx_and_report(bak, tmpidx, TAMPER_PATH);
//                         rename(bak, IDX_PATH);
//                     } else {
//                         reported = compare_oldidx_newidx_and_report(IDX_PATH, tmpidx, TAMPER_PATH);
//                     }
//                     if (reported >= 0) log_msg("tamper report written: %s (reported=%d)", TAMPER_PATH, reported);
//                 } else {
//                     log_msg("failed to rebuild tmp idx for tail mismatch");
//                 }
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return -1;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* parse chain hex to g_chain and set lines/offset */
//     memset(g_chain,0,32);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi,&lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i]= (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx for reading offset");
//         return -1;
//     }
//     char buf[1024]; unsigned long long last_off=0; unsigned long long last_i=0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i=i; last_off=off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx) return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0) return -1;

//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//             (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if (n<=0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n] = 0;

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) {
//         unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//         salt[i] = (hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                            EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx ||
//         EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx) EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1;
//     }

//     /* canonical */
//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     /* sign */
//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* certb64 */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len > 0) {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 初始化：如果没有 seal 则允许从 log 重建 idx 并初始化；如果有 seal 则严格校验 */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     /* 打开/创建 idx 文件 */
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx) {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int vs = verify_existing_seal_or_rebuild();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch with audit.log / idx. Aborting (manual recovery required).");
//         return -1;
//     }
//     if (vs > 0) {
//         /* 没有 seal：首次启动，从 log 重构 idx（允许） */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         /* 重新打开 idx 指向新文件 */
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH, "a+");
//         if (!g_idx) {
//             log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//             return -1;
//         }
//         chmod(IDX_PATH, 0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         /* 已验证通过：g_chain/g_lines/g_offset 已在 verify_existing_seal_or_rebuild 中设置 */
//         log_msg("existing seal verified. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* 当检测到截断时（tailing）: 不自动替换 idx/seal；尝试生成 tamper 报告并退出 */
// static int recover_after_truncate(void) {
//     log_msg("Recovery after truncation requested: do not auto-rebuild, produce tamper report and exit");
//     trigger_alarm("Audit log truncated or shortened - manual recovery required");
//     /* Attempt to create tmp idx and tamper report for admin use */
//     char tmpidx[PATH_MAX]; snprintf(tmpidx,sizeof(tmpidx), "%s.tmp.%ld", IDX_PATH, (long)time(NULL));
//     unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//     if (rebuild_idx_from_log(LOG_PATH, tmpidx, new_chain, &new_lines, &new_off) == 0) {
//         if (access(IDX_PATH, R_OK) == 0) {
//             char bak[PATH_MAX]; snprintf(bak, sizeof(bak), "%s.bak.%ld", IDX_PATH, (long)time(NULL));
//             if (rename(IDX_PATH, bak) == 0) {
//                 int reported = compare_oldidx_newidx_and_report(bak, tmpidx, TAMPER_PATH);
//                 if (reported >= 0) log_msg("tamper report: %s (reported=%d)", TAMPER_PATH, reported);
//                 /* restore old idx */
//                 rename(bak, IDX_PATH);
//             } else {
//                 log_msg("failed to backup idx for truncate recovery");
//             }
//         } else {
//             log_msg("no existing idx to compare; tmp idx left at %s", tmpidx);
//         }
//     } else {
//         log_msg("failed to build tmp idx during truncate recovery");
//     }
//     return -1;
// }

// /* Tail: 如果发现异常（截断、inode 变化、chain mismatch） -> 写 tamper 报告 & exit */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0; ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) {
//             sleep(1);
//             if (!g_run) break;
//             continue;
//         }
//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if (!lf) { sleep(1); continue; }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 log_msg("fseeko to %llu failed, attempt recovery", g_offset);
//                 if (recover_after_truncate() != 0) {
//                     trigger_alarm("Cannot seek to last known offset in audit.log; abort");
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 /* do not auto-resume silently; plugin will have exited in recovery */
//             }
//         }
//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line); line=NULL;
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run) break;
//         }
//         if (!g_run) break;

//         /* EOF：检查是否截断或 inode 改变，遇到则报警并退出（防止自动重签） */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) {
//             fclose(lf); lf=NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> generate tamper report and exit",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; exiting to avoid auto-resign of tampered data");
//             fclose(lf); lf=NULL;
//             /* generate tamper report */
//             recover_after_truncate(); /* this writes tamper report and returns -1 */
//             return -1;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> exit for manual review");
//             trigger_alarm("Audit log rotated; exiting to require manual review & re-init");
//             fclose(lf); lf=NULL;
//             return -1;
//         }
//         usleep(100000);
//     }
//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* normal exit */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本（新）
/* gmseal-plugin.c
 * 目标：
 *  1. 不生成 tamper 文件、不生成 idx.tmp.*
 *  2. 支持严格冻结模式：发现篡改/截断/inode替换等异常即冻结 -> 禁用 audit、保持 log/idx/seal 一致（到冻结点）
 *  3. 提供 --recover 模式：人工恢复后，重建 idx、重写 seal、解冻并继续捕获
 *  4. 能识别 vim 和 sed -i 篡改，保持一致并可验证具体被篡改行（通过 audit-verify 实现）
 */

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// /* 你的报警接口 */
// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* GM plugin log */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// /* 冻结标志文件 */
// #define FREEZE_FLAG "/var/run/audisp-gmseal.freeze"

// /* 处于冻结下时是否强制截断 audit.log 到可信前缀 */
// #define TRUNCATE_LOG_ON_RECOVER 1

// static FILE *g_log = NULL;
// static int g_run = 1;
// static int g_frozen = 0; /* 是否冻结 */

// // logging
// static void log_open(void){ mkdir(GMLOG_DIR, 0755); g_log = fopen(GMLOG_FILE,"a"); }
// static void log_msg(const char*fmt,...){
//     if(!g_log) return;
//     va_list ap; va_start(ap,fmt);
//     time_t t=time(NULL); char tb[64];
//     strftime(tb,sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ",tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log,"\n"); fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1) { if(outlen>0) out[0]=0; return; }
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem)
//     {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0)
//     {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* base64 decode */
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b||!m) { if(b)BIO_free(b); if(m)BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64)*3/4+16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n<=0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* rebuild idx: 仅用于首次启动（无 seal）或恢复模式 */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;

//     FILE *tf = fopen(idxpath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(idxpath, 0600);

//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln=0, offset=0;
//     unsigned char H[32] = {0};

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                 (unsigned long long)ln, (unsigned long long)offset, lhex, chex);

//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);

//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* 从 idx 读入内存 */
// typedef struct { unsigned long long i, off; char lhash[65], chash[65]; } IdxEntry;
// typedef struct { IdxEntry *entries; size_t count; } IdxArray;

// static void free_idx_array(IdxArray *a) { if (!a) return; free(a->entries); a->entries=NULL; a->count=0; }

// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out,0,sizeof(*out));
//     FILE *f = fopen(path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     size_t capv=0, cnt=0;
//     IdxEntry *arr=NULL;

//     while (fgets(buf,sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i=ii; arr[cnt].off=off;
//             strncpy(arr[cnt].lhash,lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash,chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr; out->count=cnt;
//     return 0;
// }

// /* 找可信前缀：log vs idx 比较链，返回最后一致行（记录二进制链、偏移）。 */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         if (out_trusted_lines) *out_trusted_lines=0;
//         if (out_offset) *out_offset=0;
//         if (out_chain) memset(out_chain,0,32);
//         return -1;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line=NULL; size_t cap=0; ssize_t n;

//     unsigned char H[32]={0};
//     unsigned long long ln=0;
//     size_t idx_pos=0;
//     unsigned long long last_good_i=0, last_good_off=0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,32);

//     while ((n=getline(&line,&cap,lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         /* chain update: H = SM3(H || normalized_line) */
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned int mdlen = 0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     last_good_i = ln;
//                     last_good_off = e->off;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                 } else {
//                     break; /* mismatch */
//                 }
//             } else if (e->i < ln) {
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos<idx.count && idx.entries[idx_pos].i==ln) {
//                     char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = ln;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                     } else break;
//                 } else {
//                     break;
//                 }
//             } else {
//                 /* idx e.i > ln (索引起始比 log 晚) -> 停止 */
//                 break;
//             }
//         } else {
//             /* idx 用光 -> 停止 */
//             break;
//         }
//     }
//     free(line);
//     fclose(lf);
//     free_idx_array(&idx);

//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);
//     return 0;
// }

// /* ========== SEAL 结构处理 ========== */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n&&(s[n-1]=='\n'||s[n-1]=='\r')) s[--n]=0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s; }

// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S,0,sizeof(*S));
//     FILE *f=fopen(seal_path,"r");
//     if(!f) return -1;
//     char *line=NULL; size_t cap=0; ssize_t n;
//     while((n=getline(&line,&cap,f))!=-1){
//         if(line[0]=='#'||line[0]==0) continue;
//         trimnl(line);
//         char*eq=strchr(line,'=');
//         if(!eq) continue;
//         *eq=0;
//         char*k=trimws(line),*v=trimws(eq+1);
//         if(strcmp(k,"version")==0) S->version=strdup(v);
//         else if(strcmp(k,"hash")==0)S->hash=strdup(v);
//         else if(strcmp(k,"sign")==0)S->sign=strdup(v);
//         else if(strcmp(k,"sm2_id")==0)S->sm2_id=strdup(v);
//         else if(strcmp(k,"path")==0)S->path=strdup(v);
//         else if(strcmp(k,"records")==0)S->records=strdup(v);
//         else if(strcmp(k,"chain")==0)S->chain=strdup(v);
//         else if(strcmp(k,"key_fpr")==0)S->key_fpr=strdup(v);
//         else if(strcmp(k,"index_sm3")==0)S->index_sm3=strdup(v);
//         else if(strcmp(k,"cert_der_base64")==0)S->cert_b64=strdup(v);
//         else if(strcmp(k,"sig_base64")==0)S->sig_b64=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->index_sm3 && S->cert_b64 && S->sig_b64) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S) {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf=NULL;
//     if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version,S->hash,S->sign,S->sm2_id,S->path,S->records,S->chain,S->key_fpr,S->index_sm3) < 0) return NULL;
//     return buf;
// }

// /* 计算前 N 行链 */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if(!sm3) return -1;
//     unsigned char H[32]={0};
//     unsigned long long scanned=0;
//     FILE *fp=fopen(path,"rb");
//     if(!fp) return -1;
//     char*line=NULL; size_t cap=0; ssize_t n;
//     while(scanned<limit_lines && (n=getline(&line,&cap,fp))!=-1){
//         size_t normlen=normalize_line(line,(size_t)n);
//         EVP_MD_CTX*ctx=EVP_MD_CTX_new();
//         if(!ctx){ fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H))==1 &&
//                   EVP_DigestUpdate(ctx, line, normlen)==1);
//         unsigned int mdlen=0;
//         if(!ok || EVP_DigestFinal_ex(ctx,H,&mdlen)!=1 || mdlen!=32){
//             EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line); fclose(fp);
//     memcpy(out32,H,32);
//     return 0;
// }

// /* 全局状态 */
// static unsigned char g_chain[32];
// static unsigned long long g_lines=0, g_offset=0;
// static FILE *g_idx=NULL;

// /* signals */
// static void on_signal(int sig){ if(sig==SIGINT||sig==SIGTERM) g_run=0; }

// /* ==================== 冻结与解冻 ==================== */
// static void set_frozen(int froz) {
//     g_frozen = froz;
//     if (froz) {
//         // 写 freeze flag
//         FILE *ff = fopen(FREEZE_FLAG,"w");
//         if (ff) { fprintf(ff,"frozen=1\n"); fclose(ff); }
//         // 禁用内核审计
//         int ret = system("auditctl -e 0 >/dev/null 2>&1");
//         log_msg("System frozen: auditctl -e 0 (ret=%d)", ret);
//     } else {
//         unlink(FREEZE_FLAG);
//         int ret = system("auditctl -e 1 >/dev/null 2>&1");
//         log_msg("System unfrozen: auditctl -e 1 (ret=%d)", ret);
//     }
// }
// static int is_frozen(void) {
//     if (g_frozen) return 1;
//     /* 如果进程被重启后，通过 FREEZE_FLAG 判断是否继承冻结状态 */
//     if (access(FREEZE_FLAG, F_OK) == 0) { g_frozen = 1; return 1; }
//     return 0;
// }

// /* ==================== 校验现有 seal 并设置状态 ===================== */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present -> allow init */
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal file parse error");
//         set_frozen(1);
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal algorithm mismatch");
//         trigger_alarm("Seal algorithm mismatch");
//         free_seal(&S);
//         set_frozen(1);
//         return -1;
//     }
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Seal cert b64 decode failed"); set_frozen(1); return -1; }
//     const unsigned char *p=cert_der;
//     X509 *cert = d2i_X509(NULL,&p,cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Seal cert der decode failed"); set_frozen(1); return -1; }

//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Seal get pubkey failed"); set_frozen(1); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Seal cert not SM2");
//         set_frozen(1);
//         return -1;
//     }
//     /* fingerprint */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if(der2len<=0) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Seal i2d_X509 failed"); set_frozen(1); return -1; }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len)!=1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen)!=1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Seal fingerprint digest failed");
//         set_frozen(1);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv,mdlen,fpr_hex,sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Seal key_fpr mismatch");
//         set_frozen(1);
//         return -1;
//     }
//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("canon build failed"); set_frozen(1); return -1; }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if(!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Seal sig b64 decode failed"); set_frozen(1); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char*)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Seal signature invalid"); set_frozen(1); return -1; }

//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for seal");
//             set_frozen(1);
//             return -1;
//         }
//         char chx[65]; hexdump(chainR,32,chx,sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             log_msg("Existing seal chain mismatch with current audit.log -> Freeze");
//             trigger_alarm("Seal chain mismatch -> Freeze");
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             set_frozen(1);
//             return -1;
//         }
//     }

//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if(sm3_file_hex(IDX_PATH, now_sm3)!=0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3");
//             set_frozen(1);
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             log_msg("Existing seal index_sm3 mismatch -> Freeze");
//             trigger_alarm("Index hash mismatch -> Freeze");
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             set_frozen(1);
//             return -1;
//         }
//         /* tail check idx last i vs seal records/chain */
//         FILE *ix=fopen(IDX_PATH,"r");
//         if(!ix) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for reading");
//             set_frozen(1);
//             return -1;
//         }
//         char buf[1024]; unsigned long long last_i=0; char last_ch[65]={0};
//         while(fgets(buf,sizeof(buf),ix)) {
//             if (buf[0]=='#') continue;
//             unsigned long long i=0,off=0; char l[128]={0}, c[128]={0};
//             if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                 last_i=i; strncpy(last_ch,c,sizeof(last_ch)-1);
//             }
//         }
//         fclose(ix);
//         if (last_i != N || strcmp(last_ch, S.chain)!=0) {
//             log_msg("seal vs idx tail mismatch -> Freeze");
//             trigger_alarm("Seal vs idx tail mismatch -> Freeze");
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             set_frozen(1);
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but seal exists");
//         set_frozen(1);
//         return -1;
//     }

//     /* parse chain */
//     memset(g_chain,0,32);
//     for(int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2],"%1x%1x",&hi,&lo)!=2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Seal chain hex parse error");
//             set_frozen(1);
//             return -1;
//         }
//         g_chain[i]=(hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);
//     /* offset from idx last off */
//     FILE *ix2 = fopen(IDX_PATH,"r");
//     if (!ix2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx to read offset");
//         set_frozen(1);
//         return -1;
//     }
//     char buf2[1024]; unsigned long long last_off=0; unsigned long long last_i2=0;
//     while(fgets(buf2,sizeof(buf2),ix2)) {
//         if(buf2[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf2,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i2=i; last_off=off;
//         }
//     }
//     fclose(ix2);
//     if (last_i2 != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("idx last_i != seal records");
//         set_frozen(1);
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* 写 seal（原子 rename） */
// static int gmseal_write_seal(void)
// {
//     if (is_frozen()) {
//         log_msg("skip writing seal due to frozen state");
//         return -1;
//     }

//     EVP_PKEY *pkey=NULL; X509 *cert=NULL;
//     /* 读取 pass */
//     FILE *pf=fopen(PASS_PATH,"r");
//     if(!pf){ log_msg("open PASS_PATH failed"); return -1; }
//     char *secret=NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if(n<=0){ free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while(n&&(secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n]=0;

//     /* 读取 salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf=fopen(SALT_PATH,"r");
//     if(!sf){ log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret,strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if(!fgets(saltbuf,sizeof(saltbuf),sf)){ fclose(sf); OPENSSL_cleanse(secret,strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while(slen&&(saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
//     if (slen != SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret,strlen(secret)); free(secret); return -1; }
//     for(size_t i=0;i<SALT_BYTES;i++){
//         unsigned int hi,lo; if(sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo)!=2){ OPENSSL_cleanse(secret,strlen(secret)); free(secret); return -1; }
//         salt[i]=(hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret,strlen(secret)); free(secret);
//         return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived,DERIVE_BYTES,passhex,sizeof(passhex));
//     OPENSSL_cleanse(secret,strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2=fopen(P12_PATH,"rb");
//     if(!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2,NULL);
//     fclose(pf2);
//     if(!p12){ log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if(!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if(derlen<=0){ EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx=EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if(!ctx || EVP_DigestInit_ex(ctx,EVP_get_digestbyname("sm3"),NULL)!=1 ||
//        EVP_DigestUpdate(ctx,der,derlen)!=1 || EVP_DigestFinal_ex(ctx,mdv,&mdlen)!=1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv,mdlen,fpr_hex,sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain,32,chain_hex,sizeof(chain_hex));
//     char idx_sm3[65]={0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3)!=0) {
//         EVP_PKEY_free(pkey); X509_free(cert);
//         log_msg("sm3 idx failed"); return -1;
//     }

//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char*)canon, strlen(canon), SM2_ID, &sig, &siglen)!=0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert);
//         return -1;
//     }
//     char *sigb64=b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len>0) {
//         certb64=b64_encode(der2,der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert);
//         return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp",SEAL_PATH);
//     FILE *sfw=fopen(tmp,"w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64);
//         return -1;
//     }
//     fprintf(sfw,"# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw,"cert_der_base64=%s\n", certb64);
//     fprintf(sfw,"sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp,0600);
//     if (rename(tmp,SEAL_PATH)!=0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64);
//         return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 处理每一行：hash + idx 追加 */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     if (is_frozen()) {
//         log_msg("frozen -> skip gmseal_on_line");
//         return -1;
//     }
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32]; unsigned int lm=0, hm=0;

//     EVP_MD_CTX *ctx=EVP_MD_CTX_new();
//     if(!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL)!=1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen)!=1 ||
//         EVP_DigestFinal_ex(ctx,lhash,&lm)!=1 || lm!=32)
//     {
//         EVP_MD_CTX_free(ctx); return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2=EVP_MD_CTX_new();
//     if(!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL)!=1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain))!=1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen)!=1 ||
//         EVP_DigestFinal_ex(ctx2,newH,&hm)!=1 || hm!=32)
//     {
//         EVP_MD_CTX_free(ctx2); return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash,32,lhex,sizeof(lhex));
//     hexdump(g_chain,32,chex,sizeof(chex));

//     if(!g_idx) return -1;
//     int fd=fileno(g_idx);
//     if(fd<0) return -1;
//     if (flock(fd, LOCK_EX)!=0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//                 (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("write idx line failed: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* 初始化：如有 seal，坚持校验；否则首次启动重建 idx */
// static int gmseal_init(void)
// {
//     memset(g_chain,0,sizeof(g_chain)); g_lines=0; g_offset=0;

//     /* 冻结标志继承 */
//     if (access(FREEZE_FLAG, F_OK) == 0) {
//         g_frozen = 1;
//         log_msg("Found freeze flag on start -> g_frozen=1");
//     }

//     g_idx = fopen(IDX_PATH,"a+");
//     if(!g_idx){
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH,0600);

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch -> Frozen. Abort init tail.");
//         return -1;
//     }
//     if (vs > 0) {
//         /* no seal: first run -> rebuild idx */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH,"a+");
//         if(!g_idx){
//             log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//             return -1;
//         }
//         chmod(IDX_PATH,0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         log_msg("existing seal verified. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* tail 处理：遇到截断/inode 改变 -> Freeze */
// static int tail_and_process(void)
// {
//     FILE *lf=NULL; char *line=NULL; size_t cap=0; ssize_t n;

//     for(;g_run;)
//     {
//         if (is_frozen()) {
//             /* 冻结时不再 tail，等待人工恢复 */
//             sleep(1);
//             continue;
//         }

//         struct stat st;
//         if(stat(LOG_PATH,&st)!=0){
//             sleep(1);
//             continue;
//         }

//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if(!lf) { sleep(1); continue; }
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET)!=0) {
//                 log_msg("fseeko to %llu failed -> Freeze", g_offset);
//                 trigger_alarm("Cannot seek to last known offset -> Freeze");
//                 set_frozen(1);
//                 fclose(lf); lf=NULL;
//                 continue;
//             }
//         }

//         while((n=getline(&line,&cap,lf))!=-1) {
//             if (n>0) {
//                 if (gmseal_on_line(line, (size_t)n)!=0) {
//                     log_msg("gmseal_on_line failed -> Freeze");
//                     trigger_alarm("Hashing error on new line -> Freeze");
//                     set_frozen(1);
//                     break;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal()!=0)
//                         log_msg("gmseal_write_seal failed (still continue)");
//                 }
//             }
//             if (!g_run || is_frozen()) break;
//         }
//         if (!g_run) break;

//         if (is_frozen()) {
//             if (line) { free(line); line=NULL; }
//             if (lf) { fclose(lf); lf=NULL; }
//             continue;
//         }

//         // EOF: 检测截断/ inode 改变
//         clearerr(lf);
//         struct stat st2;
//         if(stat(LOG_PATH,&st2)!=0){
//             fclose(lf); lf=NULL; sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated (size %llu < last offset %llu) -> Freeze",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated -> Freeze");
//             set_frozen(1);
//             fclose(lf); lf=NULL;
//             continue;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> Freeze");
//             trigger_alarm("Audit log rotated -> Freeze");
//             set_frozen(1);
//             fclose(lf); lf=NULL;
//             continue;
//         }
//         usleep(100000);
//     }

//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// /* 恢复逻辑：找到可信前缀 -> 截断 -> 重建 idx -> 重写 seal -> 解冻 */
// static int do_recover(void)
// {
//     log_open();
//     log_msg("audisp-gmseal --recover started");

//     /* 1) 找到可信前缀 */
//     unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//     memset(trusted_chain,0,32);

//     if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off)!=0) {
//         log_msg("find_trusted_prefix failed: cannot recover");
//         return 2;
//     }
//     log_msg("trusted prefix found: lines=%llu off=%llu", trusted_lines, trusted_off);

// #if TRUNCATE_LOG_ON_RECOVER
//     /* 2) 截断 audit.log 到 trusted_off（强一致） */
//     int fd = open(LOG_PATH, O_WRONLY);
//     if (fd >= 0) {
//         if (ftruncate(fd, (off_t)trusted_off)!=0) {
//             log_msg("ftruncate audit.log to %llu failed: %s", trusted_off, strerror(errno));
//             close(fd);
//             return 2;
//         }
//         close(fd);
//         log_msg("audit.log truncated to offset=%llu", trusted_off);
//     } else {
//         log_msg("open audit.log for truncate failed: %s", strerror(errno));
//         return 2;
//     }
// #endif

//     /* 3) 重建 idx（用截断后的 log） */
//     unsigned char chain2[32]; unsigned long long lines2=0, off2=0;
//     if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, chain2, &lines2, &off2)!=0) {
//         log_msg("rebuild_idx_from_log failed during recovery");
//         return 2;
//     }
//     log_msg("idx rebuilt: lines=%llu off=%llu", lines2, off2);

//     /* 4) 设置全局状态并重写 seal */
//     memcpy(g_chain, chain2, 32);
//     g_lines = lines2;
//     g_offset = off2;

//     if (gmseal_write_seal()!=0) {
//         log_msg("write seal failed during recovery");
//         return 2;
//     }

//     /* 5) 解冻并开启审计 */
//     set_frozen(0);
//     log_msg("audisp-gmseal --recover finished");
//     return 0;
// }

// /* main */
// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     if (argc == 2 && strcmp(argv[1],"--recover")==0) {
//         /* 人工恢复模式 */
//         int rc = do_recover();
//         EVP_cleanup(); ERR_free_strings();
//         return rc;
//     }

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init()!=0) {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init()!=0) {
//         log_msg("gmseal_init failed. If frozen, run 'audisp-gmseal --recover' after inspection.");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* 初始写 seal */
//     if (!is_frozen()) {
//         if (gmseal_write_seal()!=0) {
//             log_msg("initial seal write failed");
//         }
//     }

//     /* 主循环 */
//     if (tail_and_process()!=0) {
//         log_msg("tail_and_process aborted");
//     }

//     /* 收尾 */
//     if (!is_frozen()) {
//         if (gmseal_write_seal()!=0) log_msg("final seal write failed");
//     }
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本9
/* 完整 gmseal-plugin.c
 *
 * 改动要点：
 *  - 检测到严重不一致（inode change / truncation / chain mismatch）时进入 freeze 状态
 *  - freeze 时停止继续写 idx / seal，并写标志文件 / 提示报警
 *  - 提供 --recover 命令：管理员手动恢复（找可信前缀 -> 可选截断 -> 重建 idx -> 重写 seal -> 解冻）
 *  - 不产生长期 .tamper 或 .idx.tmp.* 文件（seal 使用 .tmp 原子重命名）
 *
 *  请在部署前调整常量路径与策略（例如是否截断日志）
 */

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* plugin 日志 */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// /* 冻结标志文件 */
// #define FREEZE_FLAG "/var/run/audisp-gmseal.freeze"

// /* 恢复时是否截断 audit.log 到可信偏移（截断确保一致性） */
// #define TRUNCATE_LOG_ON_RECOVER 1

// static FILE *g_log = NULL;
// static void log_open(void) {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...) {
//     if (!g_log) return;
//     va_list ap; va_start(ap, fmt);
//     time_t t = time(NULL); char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen) {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1) { if(outlen>0) out[0]=0; return; }
//     for (size_t i = 0; i < len; i++) {
//         out[i*2]   = hex[(buf[i]>>4)&0xF];
//         out[i*2+1] = hex[buf[i]&0xF];
//     }
//     out[len*2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len) {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) { if(b64)BIO_free(b64); if(mem)BIO_free(mem); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) { BIO_free_all(b64); return NULL; }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* base64 decode */
// static unsigned char *b64_decode(const char *b64, size_t *olen) {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b||!m) { if(b)BIO_free(b); if(m)BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64)*3/4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// /* normalize */
// static size_t normalize_line(char *buf, size_t len) {
//     while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
//     return len;
// }

// /* sm3 file hex */
// static int sm3_file_hex(const char *path, char out_hex[65]) {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path,"rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096]; size_t n;
//     while ((n=fread(buf,1,sizeof(buf),f))>0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen=0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* count lines (simple) */
// static unsigned long long count_file_lines_simple(const char *path) {
//     FILE *f = fopen(path,"rb");
//     if (!f) return 0;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     unsigned long long ln=0;
//     while ((n=getline(&line,&cap,f))!=-1) ln++;
//     free(line); fclose(f); return ln;
// }

// /* rebuild idx from log (used only on first-run or recover) */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset) {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3"); if (!sm3) return -1;
//     FILE *lf = fopen(logpath,"rb"); if (!lf) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     unsigned long long ln=0, offset=0;
//     unsigned char H[32]; memset(H,0,sizeof(H));

//     /* 为了安全，写到临时文件再 rename（短暂存在 .tmp 文件） */
//     char tmp[PATH_MAX]; snprintf(tmp, sizeof(tmp), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmp,"w"); if (!tf) { fclose(lf); return -1; }
//     chmod(tmp, 0600);

//     while ((n=getline(&line,&cap,lf))!=-1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         /* compute lhash */
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL)!=1 || EVP_DigestUpdate(c1, line, normlen)!=1 || EVP_DigestFinal_ex(c1, L, &lm)!=1 || lm!=32) {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmp); free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         /* update chain */
//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL)!=1 || EVP_DigestUpdate(c2, H, sizeof(H))!=1 || EVP_DigestUpdate(c2, line, normlen)!=1 || EVP_DigestFinal_ex(c2, newH, &hm)!=1 || hm!=32) {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmp); free(line);
//             return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L,32,lhex,sizeof(lhex));
//         hexdump(H,32,chex,sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
//             fclose(lf); fclose(tf); unlink(tmp); free(line);
//             return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmp, idxpath) != 0) {
//         unlink(tmp);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* Global state */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* signals */
// static void on_signal(int sig) { if (sig==SIGINT||sig==SIGTERM) g_run = 0; }

// /* Idx structures for trusted prefix search */
// typedef struct { unsigned long long i; unsigned long long off; char lhash[65]; char chash[65]; } IdxEntry;
// typedef struct { IdxEntry *entries; size_t count; } IdxArray;
// static void free_idx_array(IdxArray *a) { if (!a) return; free(a->entries); a->entries=NULL; a->count=0; }

// /* load idx entries */
// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out,0,sizeof(*out));
//     FILE *f = fopen(path,"r"); if (!f) return -1;
//     char buf[1024];
//     IdxEntry *arr = NULL; size_t cnt=0, capv=0;
//     while (fgets(buf,sizeof(buf),f)) {
//         if (buf[0]=='#') continue;
//         unsigned long long ii=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii; arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr; out->count = cnt;
//     return 0;
// }

// /* 找可信前缀（与之前版本相同的逻辑） */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset) {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_offset) *out_offset = 0;
//         if (out_chain) memset(out_chain, 0, 32);
//         return -1;
//     }
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }
//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line=NULL; size_t cap=0; ssize_t n;
//     unsigned char H[32]; memset(H,0,sizeof(H));
//     unsigned long long ln=0;
//     size_t idx_pos=0;
//     unsigned long long last_good_i=0, last_good_off=0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));
//     while ((n=getline(&line,&cap,lf))!=-1) {
//         ln++;
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned int mdlen=0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL)!=1 || EVP_DigestUpdate(ctx, H, sizeof(H))!=1 || EVP_DigestUpdate(ctx, line, normlen)!=1 || EVP_DigestFinal_ex(ctx, H, &mdlen)!=1 || mdlen!=32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx); return -1;
//         }
//         EVP_MD_CTX_free(ctx);
//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     last_good_i = ln; last_good_off = e->off; memcpy(last_good_chain, H, 32);
//                     idx_pos++; continue;
//                 } else break;
//             } else if (e->i < ln) {
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = ln; last_good_off = idx.entries[idx_pos].off; memcpy(last_good_chain, H, 32); idx_pos++; continue;
//                     } else break;
//                 } else break;
//             } else break;
//         } else break;
//     }
//     free(line); fclose(lf);
//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);
//     free_idx_array(&idx);
//     return 0;
// }

// /* seal parsing / signing helpers (与原逻辑一致) */
// struct seal { char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64; };
// static void trimnl(char *s) { size_t n=strlen(s); while(n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n]=0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s; }
// static int parse_seal_file(const char *seal_path, struct seal *S) {
//     memset(S,0,sizeof(*S));
//     FILE *f = fopen(seal_path,"r"); if (!f) return -1;
//     char *line=NULL; size_t cap=0; ssize_t n;
//     while ((n=getline(&line,&cap,f))!=-1) {
//         if (line[0]=='#' || line[0]==0) continue;
//         trimnl(line); char *eq=strchr(line,'='); if (!eq) continue;
//         *eq=0; char *k=trimws(line); char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S) {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }

// /* decode b64 */
// static unsigned char *b64_decode_local(const char *b64, size_t *olen) { return b64_decode(b64, olen); }

// /* compute chain for first N lines (used to verify existing seal) */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32]) {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3"); if (!sm3) return -1;
//     unsigned char H[32]; memset(H,0,sizeof(H));
//     unsigned long long scanned=0;
//     FILE *fp = fopen(path,"rb"); if (!fp) return -1;
//     char *line=NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n=getline(&line,&cap,fp))!=-1) {
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL)==1 && EVP_DigestUpdate(ctx, H, sizeof(H))==1 && EVP_DigestUpdate(ctx, line, normlen)==1);
//         unsigned int mdlen=0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen)!=1 || mdlen!=32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line); fclose(fp);
//     memcpy(out32, H, 32);
//     return 0;
// }

// /* freeze management */
// static void set_freeze_flag() {
//     FILE *f = fopen(FREEZE_FLAG,"w"); if (f) { fprintf(f,"frozen=1\n"); fclose(f); }
//     log_msg("SYSTEM FREEZE FLAG SET");
// }
// static void clear_freeze_flag() {
//     unlink(FREEZE_FLAG);
//     log_msg("SYSTEM FREEZE FLAG CLEARED");
// }
// static int is_frozen() {
//     return access(FREEZE_FLAG, F_OK) == 0;
// }

// /* verify existing seal (行为修改：若不一致 -> 进入 freeze 而不是继续写入导致更大错乱) */
// static int verify_existing_seal(void) {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: first-run");
//         return 1;
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         set_freeze_flag();
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal algorithm mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S); set_freeze_flag(); return -1;
//     }
//     size_t cert_len=0; unsigned char *cert_der = b64_decode_local(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert decode failed"); set_freeze_flag(); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len); free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); set_freeze_flag(); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); set_freeze_flag(); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2"); set_freeze_flag(); return -1;
//     }
//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("i2d_X509 failed"); set_freeze_flag(); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 || EVP_DigestUpdate(ctx, der2, der2len)!=1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen)!=1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der2); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("fingerprint digest failed"); set_freeze_flag(); return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         set_freeze_flag(); return -1;
//     }
//     /* verify signature */
//     char *canon = NULL;
//     if (asprintf(&canon, "version=%s\nhash=%s\nsign=%s\nsm2_id=%s\npath=%s\nrecords=%s\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
//                  S.version, S.hash, S.sign, S.sm2_id, S.path, S.records, S.chain, S.key_fpr, S.index_sm3) < 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("asprintf failed"); set_freeze_flag(); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode_local(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("sig decode failed"); set_freeze_flag(); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char*)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("seal signature invalid"); set_freeze_flag(); return -1; }

//     /* chain check */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("chain computation failed"); set_freeze_flag(); return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             log_msg("Existing seal chain mismatch -> entering freeze");
//             trigger_alarm("Existing seal chain mismatch; freeze required");
//             /* 进入 freeze，要求人工恢复 */
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             set_freeze_flag();
//             return -1;
//         }
//     }

//     /* idx check: ensure idx exists and matches index_sm3 and tail */
//     if (access(IDX_PATH, R_OK) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but seal expects one");
//         set_freeze_flag();
//         return -1;
//     }
//     char now_sm3[65];
//     if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot compute idx sm3"); set_freeze_flag(); return -1;
//     }
//     if (strcmp(now_sm3, S.index_sm3) != 0) {
//         log_msg("Existing seal index_sm3 mismatch -> freeze");
//         trigger_alarm("index_sm3 mismatch -> freeze required");
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         set_freeze_flag();
//         return -1;
//     }
//     /* read last entry in idx to compute offset */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("open idx failed"); set_freeze_flag(); return -1; }
//     char buf[1024]; unsigned long long last_i=0, last_off=0;
//     while (fgets(buf, sizeof(buf), ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4) {
//             last_i = i; last_off = off;
//         }
//     }
//     fclose(ix);
//     if (last_i != (unsigned long long)strtoull(S.records, NULL, 10)) {
//         log_msg("idx last_i != seal records -> freeze");
//         trigger_alarm("idx last_i != seal.records -> freeze required");
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         set_freeze_flag();
//         return -1;
//     }
//     /* parse chain into g_chain and set g_lines/g_offset */
//     memset(g_chain, 0, sizeof(g_chain));
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi, &lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("chain hex parse error"); set_freeze_flag(); return -1;
//         }
//         g_chain[i] = (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records, NULL, 10);
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* write seal (atomic) - only if not frozen */
// static int gmseal_write_seal(void) {
//     if (is_frozen()) {
//         log_msg("skip writing seal because frozen");
//         return -1;
//     }
//     EVP_PKEY *pkey = NULL; X509 *cert = NULL;
//     /* read pass */
//     FILE *pf = fopen(PASS_PATH,"r"); if (!pf) { log_msg("open PASS failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n = getline(&secret, &cap, pf); fclose(pf);
//     if (n <= 0) { free(secret); log_msg("empty pass"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n]=0;
//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH,"r"); if (!sf) { OPENSSL_cleanse(secret,strlen(secret)); free(secret); log_msg("open SALT failed"); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret,strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen = strlen(saltbuf); while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
//     if (slen != (size_t)SALT_BYTES*2) { OPENSSL_cleanse(secret,strlen(secret)); free(secret); log_msg("salt len error"); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) { unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo)!=2) { OPENSSL_cleanse(secret,strlen(secret)); free(secret); return -1; } salt[i]=(hi<<4)|lo; }
//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived)) {
//         OPENSSL_cleanse(secret,strlen(secret)); free(secret); log_msg("PBKDF2 failed"); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH,"rb"); if (!pf2) { log_msg("open P12 failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) { PKCS12_free(p12); log_msg("PKCS12_parse failed"); return -1; }
//     PKCS12_free(p12);

//     /* cert fingerprint */
//     unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 || EVP_DigestUpdate(ctx, der, derlen)!=1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen)!=1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));
//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) { EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1; }

//     char *canon = NULL;
//     if (asprintf(&canon, "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
//                  SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     unsigned char *sig = NULL; size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen); OPENSSL_free(sig);

//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     char *certb64 = NULL;
//     if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); } else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) { log_msg("rename seal tmp failed: %s", strerror(errno)); unlink(tmp); free(canon); free(sigb64); free(certb64); return -1; }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* gmseal_on_line (append to idx) */
// static int gmseal_on_line(char *line_buf, size_t raw_n) {
//     if (is_frozen()) { log_msg("frozen -> skip line processing"); return -1; }
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3"); if (!sm3) return -1;
//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32]; unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL)!=1 || EVP_DigestUpdate(ctx, line_buf, normlen)!=1 || EVP_DigestFinal_ex(ctx, lhash, &lm)!=1 || lm!=32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new(); if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL)!=1 || EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain))!=1 || EVP_DigestUpdate(ctx2, line_buf, normlen)!=1 || EVP_DigestFinal_ex(ctx2, newH, &hm)!=1 || hm!=32) { EVP_MD_CTX_free(ctx2); return -1; }
//     EVP_MD_CTX_free(ctx2);
//     memcpy(g_chain, newH, 32);
//     g_lines++;
//     char lhex[65], chex[65]; hexdump(lhash,32,lhex,sizeof(lhex)); hexdump(g_chain,32,chex,sizeof(chex));
//     if (!g_idx) return -1;
//     int fd = fileno(g_idx); if (fd < 0) return -1;
//     if (flock(fd, LOCK_EX) != 0) log_msg("flock EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0) {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);
//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* init: 打开 idx，并验证 seal；如无 seal（first run）允许重建 idx */
// static int gmseal_init(void) {
//     memset(g_chain,0,sizeof(g_chain)); g_lines=0; g_offset=0;
//     /* 若 freeze flag 已存在，保留 freeze 状态（等待人工恢复） */
//     if (is_frozen()) log_msg("start with freeze flag present -> not processing new lines until recover");
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx) { log_msg("open %s failed: %s", IDX_PATH, strerror(errno)); return -1; }
//     chmod(IDX_PATH, 0600);
//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("verify_existing_seal failed -> freeze");
//         return -1;
//     }
//     if (vs > 0) {
//         /* first run, rebuild idx from log */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH, "a+");
//         if (!g_idx) { log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno)); return -1; }
//         chmod(IDX_PATH, 0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         log_msg("existing seal verified. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* tail_and_process：检测到截断/inode改变/seek失败等 -> don't try to auto-continue; set freeze and require manual recover */
// static int tail_and_process(void) {
//     FILE *lf = NULL; char *line = NULL; size_t cap=0; ssize_t n;
//     for (; g_run;) {
//         if (is_frozen()) { sleep(1); continue; }
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) { sleep(1); continue; }
//         if (!lf) {
//             lf = fopen(LOG_PATH, "rb");
//             if (!lf) { sleep(1); continue; }
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 log_msg("fseeko to %llu failed: entering freeze and requesting manual recovery", g_offset);
//                 trigger_alarm("Cannot seek to last known offset -> freeze and manual recover required");
//                 set_freeze_flag();
//                 fclose(lf); lf = NULL;
//                 continue;
//             }
//         }
//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed -> entering freeze");
//                     trigger_alarm("Hashing error on new line -> freeze and manual recovery required");
//                     set_freeze_flag();
//                     free(line); line = NULL;
//                     fclose(lf); lf = NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0) log_msg("gmseal_write_seal failed (but continuing)");
//                 }
//             }
//             if (!g_run || is_frozen()) break;
//         }
//         if (!g_run) break;
//         if (is_frozen()) { if (line) { free(line); line=NULL; } if (lf) { fclose(lf); lf=NULL; } continue; }
//         /* EOF: check truncation/inode change */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) { fclose(lf); lf=NULL; sleep(1); continue; }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> freeze", (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated -> freeze and manual recovery required");
//             set_freeze_flag();
//             fclose(lf); lf=NULL;
//             continue;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation/replace) -> freeze");
//             trigger_alarm("Audit log inode changed -> freeze and manual recovery required");
//             set_freeze_flag();
//             fclose(lf); lf=NULL;
//             continue;
//         }
//         usleep(100000);
//     }
//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// /* recover: 手工恢复流程（管理员运行） */
// static int do_recover(void) {
//     log_open();
//     log_msg("audisp-gmseal --recover started");
//     unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//     memset(trusted_chain,0,sizeof(trusted_chain));
//     if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) != 0) {
//         log_msg("find_trusted_prefix failed: cannot recover automatically");
//         return 2;
//     }
//     log_msg("trusted prefix: lines=%llu off=%llu", trusted_lines, trusted_off);
// #if TRUNCATE_LOG_ON_RECOVER
//     /* truncate audit.log to trusted_off */
//     int fd = open(LOG_PATH, O_WRONLY);
//     if (fd >= 0) {
//         if (ftruncate(fd, (off_t)trusted_off) != 0) {
//             log_msg("ftruncate audit.log failed: %s", strerror(errno));
//             close(fd);
//             return 2;
//         }
//         close(fd);
//         log_msg("audit.log truncated to %llu", trusted_off);
//     } else {
//         log_msg("open audit.log for truncate failed: %s", strerror(errno));
//         return 2;
//     }
// #endif
//     /* rebuild idx from truncated log */
//     unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//     if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//         log_msg("rebuild_idx_from_log failed during recover");
//         return 2;
//     }
//     log_msg("idx rebuilt: lines=%llu off=%llu", new_lines, new_off);
//     /* set global state */
//     memcpy(g_chain, new_chain, 32);
//     g_lines = new_lines;
//     g_offset = new_off;
//     /* write seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("write seal failed during recover");
//         return 2;
//     }
//     /* clear freeze flag and attempt to re-enable auditd (建议管理员确认) */
//     clear_freeze_flag();
//     log_msg("recover finished: please ensure auditd is running and monitoring resumed");
//     return 0;
// }

// /* main */
// int main(int argc, char **argv) {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     /* recover mode */
//     if (argc == 2 && strcmp(argv[1], "--recover") == 0) {
//         int rc = do_recover();
//         EVP_cleanup(); ERR_free_strings();
//         return rc;
//     }

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0) {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0) {
//         log_msg("gmseal_init failed (abort). If frozen, run 'audisp-gmseal --recover' after inspection.");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal (if not frozen) */
//     if (!is_frozen()) {
//         if (gmseal_write_seal() != 0) log_msg("initial seal write failed");
//     } else {
//         log_msg("start-up is frozen; waiting for manual recovery");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* final */
//     if (!is_frozen()) {
//         if (gmseal_write_seal() != 0) log_msg("final seal write failed");
//     }
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup(); ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本（三权模式下）
//  #define _GNU_SOURCE
//  #include <stdio.h>
//  #include <stdlib.h>
//  #include <stdarg.h>
//  #include <stdint.h>
//  #include <unistd.h>
//  #include <string.h>
//  #include <errno.h>
//  #include <signal.h>
//  #include <time.h>
//  #include <limits.h>
//  #include <sys/stat.h>
//  #include <sys/file.h>
//  #include <sys/types.h>
//  #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"     /* trigger_alarm */
// #include "../../lib/gm_crypto.h"   /* gm_sm2_sign, gm_sm2_verify, gm_crypto_init etc. */

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* base64 decode (no newlines) */
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx: 提供工具函数，但本修复版仅在首次无seal时用于初始化 */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0) {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* ------------ Global state ------------ */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* signals */
// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* ----------------- 新增：读取 idx 到内存，用以查找 trusted prefix ----------------- */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL;
//     a->count = 0;
// }

// /* 从 idx 文件读取到内存（简单 parser），返回 0 成功 */
// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out, 0, sizeof(*out));
//     FILE *f = fopen(path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     /* 先收集条目到动态数组 */
//     IdxEntry *arr = NULL;
//     size_t cnt = 0, capv = 0;
//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii;
//             arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr;
//     out->count = cnt;
//     return 0;
// }

// /* 找到可信前缀：返回最后匹配的 idx 行数（0 表示没有匹配），并通过 out_chain/out_off 返回对应二进制 chain 与 偏移（若 matched == 0，chain 置 0，off 置 0）
//    算法：逐行读取 audit.log，在每次读到第 k 行时计算 chain，比较当前 idx 中的 chash（如果存在）。当出现第一次不匹配时（或者 idx 早已没有 k 行）则停止，trusted = k-1。
// */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         /* 无 idx 文件或无法读取 -> no trust */
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_offset) *out_offset = 0;
//         if (out_chain) memset(out_chain, 0, 32);
//         return -1;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32];
//     memset(H, 0, sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0;
//     unsigned long long last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         /* update chain: H = SM3(H || normalized_line) */
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned int mdlen = 0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         /* see if idx has an entry for this ln (idx entries might start with i=1 etc) */
//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 /* compare H (hex) with e->chash */
//                 char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     /* matched */
//                     last_good_i = e->i;
//                     last_good_off = e->off + 0;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else {
//                     /* mismatch here -> stop: trusted = ln-1 */
//                     break;
//                 }
//             } else if (e->i < ln) {
//                 /* idx has extra earlier entries we already passed - advance idx_pos until >= ln or end */
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else {
//                         break;
//                     }
//                 } else {
//                     /* no idx entry for this line -> can't trust beyond previous matched */
//                     break;
//                 }
//             } else {
//                 /* idx_pos points to an entry with i > ln (unlikely normally) -> means idx starts later -> can't trust current ln */
//                 break;
//             }
//         } else {
//             /* idx ended -> no more trust */
//             break;
//         }
//     }

//     free(line);
//     fclose(lf);

//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);

//     free_idx_array(&idx);
//     return 0;
// }

// /* ---------- 现有代码：解析 seal 并验证，但当发现链/idx不一致时改为“降级运行”（定位 trusted prefix 并继续） ---------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k=trimws(line);
//         char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// /* 计算前 N 行链 */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//     {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen=0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     return 0;
// }

// /* 验证现有seal，若存在。
//    行为调整：
//     - 如果没有 seal: 返回 1（first run，允许初始化）
//     - 如果 seal 存在且一致: 返回 0
//     - 如果 seal 存在但 chain/index 与文件不一致:  *不会直接失败/退出*
//           而是定位可信前缀（trusted lines）并将全局状态设置到 trusted 前缀，返回 0（继续运行，但已告警）
// */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present, allow init from scratch */
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }
//     /* verify signature */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }
//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     /* chain check: recompute chain on log first N lines */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             /* 链不匹配 -> 进入降级处理：尝试定位 trusted prefix 并继续运行 */
//             log_msg("Existing seal chain mismatch with current audit.log; attempting to compute trusted prefix and continue in degraded mode");
//             trigger_alarm("Existing seal chain mismatch with audit.log; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix found; set trusted_lines=0 and continue (manual intervention required)");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//     } else {
//         /* records=0, ok */
//     }

//     /* idx check */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             /* idx 被修改，报警并尝试定位可信前缀，同上 */
//             log_msg("Existing seal index_sm3 mismatch - idx file changed; attempting to find trusted prefix and continue");
//             trigger_alarm("Index file hash mismatch; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located after idx mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//         /* tail check */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65]={0};
//             while (fgets(buf, sizeof(buf), ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("Existing seal vs idx tail mismatch; try to find trusted prefix and continue");
//                 trigger_alarm("Seal vs idx tail mismatch; degraded mode (continue) - manual review required");

//                 unsigned long long trusted_lines = 0;
//                 unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//                 if (ret != 0) {
//                     log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                     memset(g_chain,0,sizeof(g_chain));
//                     g_lines = 0;
//                     g_offset = 0;
//                 } else {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Trusted prefix located after idx tail mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//                 }
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* parse chain into g_chain */
//     memset(g_chain,0,32);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi,&lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i]= (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx for reading offset");
//         return -1;
//     }
//     char buf[1024]; unsigned long long last_off=0; unsigned long long last_i=0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i=i; last_off=off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx) return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0) return -1;

//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//             (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if (n<=0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n] = 0;

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) {
//         unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//         salt[i] = (hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                            EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx ||
//         EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx) EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1;
//     }

//     /* canonical */
//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     /* sign */
//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* certb64 */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len > 0) {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 初始化：不再自动重建 idx；如有 existing seal 必须严格匹配或进入降级模式（trusted prefix） */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     /* 打开/创建 idx 文件 */
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx) {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch with audit.log / idx. Aborting.");
//         return -1;
//     }
//     if (vs > 0) {
//         /* 没有 seal，首次启动：从 log 重构 idx（允许） */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         /* 重新打开 idx 指向新文件 */
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH, "a+");
//         if (!g_idx) {
//             log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//             return -1;
//         }
//         chmod(IDX_PATH, 0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         /* seal 验证成功或已在 verify_existing_seal 中设置 trusted prefix 状态 */
//         log_msg("existing seal verified or degraded-mode prepared. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* Tail: 当检测到截断/inode 改变等问题时，进入“尝试恢复 trusted prefix 并继续”而非直接 exit */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0; ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) {
//             sleep(1);
//             if (!g_run) break;
//             continue;
//         }
//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if (!lf) { sleep(1); continue; }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 /* 不再直接 exit，而是尝试重新定位 trusted prefix */
//                 log_msg("fseeko to %llu failed: attempt to recover trusted prefix", g_offset);
//                 trigger_alarm("Cannot seek to last known offset in audit.log; attempting recovery and continuing");
//                 /* try find trusted prefix using idx and log */
//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Recovery: set trusted state lines=%llu off=%llu and reopen", g_lines, g_offset);
//                     /* reopen lf and seek again */
//                     fclose(lf); lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { log_msg("reopen log failed after recovery"); return -1; }
//                     if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                         log_msg("seek failed even after recovery; set to file end and continue");
//                         fseeko(lf, 0, SEEK_END);
//                         g_offset = (unsigned long long)ftello(lf);
//                     }
//                 } else {
//                     log_msg("Recovery attempt failed: no trusted prefix found; seeking to file end and continue");
//                     fclose(lf);
//                     lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { sleep(1); continue; }
//                     fseeko(lf, 0, SEEK_END);
//                     g_offset = (unsigned long long)ftello(lf);
//                 }
//             }
//         }

//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line); line=NULL;
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run) break;
//         }

//         if (!g_run) break;

//         /* EOF：检查是否截断或 inode 改变，遇到则报警并尝试恢复（而非直接退出） */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) {
//             fclose(lf); lf=NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> attempt recovery and continue",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; attempting recovery and continue (manual review required)");
//             /* attempt to locate trusted prefix and set g_offset appropriately */
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after truncation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 log_msg("Recovery after truncation failed: no trusted prefix; setting g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain,0,sizeof(g_chain));
//                 g_lines = 0;
//             }
//             /* reposition lf for next loop */
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> attempt recovery and continue");
//             trigger_alarm("Audit log rotated; attempting recovery and continue (manual review required)");
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after rotation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 /* fallback: start from file end */
//                 log_msg("Recovery after rotation failed: no trusted prefix; set g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//             }
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         usleep(100000);
//     }

//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* normal exit */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }

// 版本（xxx）
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"   /* trigger_alarm */
// #include "../../lib/gm_crypto.h" /* gm_sm2_sign, gm_sm2_verify, gm_crypto_init etc. */

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//    mkdir(GMLOG_DIR, 0755);
//    g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//    if (!g_log)
//       return;
//    va_list ap;
//    va_start(ap, fmt);
//    time_t t = time(NULL);
//    char tb[64];
//    strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//    fprintf(g_log, "[%s] ", tb);
//    vfprintf(g_log, fmt, ap);
//    fprintf(g_log, "\n");
//    fflush(g_log);
//    va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//    static const char hex[] = "0123456789abcdef";
//    if (outlen < len * 2 + 1)
//       return;
//    for (size_t i = 0; i < len; i++)
//    {
//       out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//       out[i * 2 + 1] = hex[buf[i] & 0xF];
//    }
//    out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//    BIO *b64 = BIO_new(BIO_f_base64());
//    BIO *mem = BIO_new(BIO_s_mem());
//    if (!b64 || !mem)
//    {
//       if (b64)
//          BIO_free(b64);
//       if (mem)
//          BIO_free(mem);
//       return NULL;
//    }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//    BIO_push(b64, mem);
//    if (BIO_write(b64, buf, (int)len) <= 0)
//    {
//       BIO_free_all(b64);
//       return NULL;
//    }
//    BIO_flush(b64);
//    BUF_MEM *bptr = NULL;
//    BIO_get_mem_ptr(b64, &bptr);
//    char *out = malloc(bptr->length + 1);
//    if (!out)
//    {
//       BIO_free_all(b64);
//       return NULL;
//    }
//    memcpy(out, bptr->data, bptr->length);
//    out[bptr->length] = 0;
//    BIO_free_all(b64);
//    return out;
// }

// /* base64 decode (no newlines) */
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//    BIO *b = BIO_new(BIO_f_base64());
//    BIO *m = BIO_new_mem_buf(b64, -1);
//    if (!b || !m)
//    {
//       if (b)
//          BIO_free(b);
//       if (m)
//          BIO_free(m);
//       return NULL;
//    }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//    BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//    BIO_push(b, m);
//    size_t cap = strlen(b64) * 3 / 4 + 16;
//    unsigned char *buf = malloc(cap);
//    int n = BIO_read(b, buf, (int)cap);
//    BIO_free_all(b);
//    if (n <= 0)
//    {
//       free(buf);
//       return NULL;
//    }
//    *olen = (size_t)n;
//    return buf;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;
//    FILE *f = fopen(path, "rb");
//    if (!f)
//       return -1;
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    if (!ctx)
//    {
//       fclose(f);
//       return -1;
//    }
//    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//    {
//       EVP_MD_CTX_free(ctx);
//       fclose(f);
//       return -1;
//    }
//    unsigned char buf[4096];
//    size_t n;
//    while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//    {
//       if (EVP_DigestUpdate(ctx, buf, n) != 1)
//       {
//          EVP_MD_CTX_free(ctx);
//          fclose(f);
//          return -1;
//       }
//    }
//    fclose(f);
//    unsigned char md[32];
//    unsigned int mdlen = 0;
//    if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//    {
//       EVP_MD_CTX_free(ctx);
//       return -1;
//    }
//    EVP_MD_CTX_free(ctx);
//    hexdump(md, 32, out_hex, 65);
//    return 0;
// }

// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//    while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//       len--;
//    return len;
// }
// static unsigned long long count_file_lines_simple(const char *path)
// {
//    FILE *f = fopen(path, "rb");
//    if (!f)
//       return 0;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    unsigned long long ln = 0;
//    while ((n = getline(&line, &cap, f)) != -1)
//       ln++;
//    free(line);
//    fclose(f);
//    return ln;
// }

// /* rebuild idx: 提供工具函数，但本修复版仅在首次无seal时用于初始化 */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;
//    FILE *lf = fopen(logpath, "rb");
//    if (!lf)
//       return -1;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    unsigned long long ln = 0, offset = 0;
//    unsigned char H[32] = {0};

//    char tmppath[PATH_MAX];
//    snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//    FILE *tf = fopen(tmppath, "w");
//    if (!tf)
//    {
//       fclose(lf);
//       return -1;
//    }
//    chmod(tmppath, 0600);

//    while ((n = getline(&line, &cap, lf)) != -1)
//    {
//       ln++;
//       size_t normlen = normalize_line(line, (size_t)n);

//       EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//       unsigned char L[32];
//       unsigned int lm = 0;
//       if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//           EVP_DigestUpdate(c1, line, normlen) != 1 ||
//           EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//       {
//          if (c1)
//             EVP_MD_CTX_free(c1);
//          fclose(lf);
//          fclose(tf);
//          unlink(tmppath);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(c1);

//       EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//       unsigned char newH[32];
//       unsigned int hm = 0;
//       if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//           EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//           EVP_DigestUpdate(c2, line, normlen) != 1 ||
//           EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//       {
//          if (c2)
//             EVP_MD_CTX_free(c2);
//          fclose(lf);
//          fclose(tf);
//          unlink(tmppath);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(c2);
//       memcpy(H, newH, 32);

//       char lhex[65], chex[65];
//       hexdump(L, 32, lhex, sizeof(lhex));
//       hexdump(H, 32, chex, sizeof(chex));
//       if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                   (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//       {
//          fclose(lf);
//          fclose(tf);
//          unlink(tmppath);
//          free(line);
//          return -1;
//       }
//       offset += (unsigned long long)n;
//    }
//    free(line);
//    fclose(lf);
//    fflush(tf);
//    fsync(fileno(tf));
//    fclose(tf);
//    if (rename(tmppath, idxpath) != 0)
//    {
//       unlink(tmppath);
//       return -1;
//    }
//    chmod(idxpath, 0600);
//    if (out_chain)
//       memcpy(out_chain, H, 32);
//    if (out_lines)
//       *out_lines = ln;
//    if (out_offset)
//       *out_offset = offset;
//    return 0;
// }

// /* truncate idx to keep only entries with i <= keep_i.
//    If keep_i == 0, produce an empty idx file (no i= lines). */
// static int truncate_idx_to_i(const char *idxpath, unsigned long long keep_i)
// {
//    FILE *in = fopen(idxpath, "r");
//    if (!in)
//    {
//       /* if idx doesn't exist and keep_i==0 it's fine */
//       if (keep_i == 0)
//       {
//          FILE *out = fopen(idxpath, "w");
//          if (!out)
//             return -1;
//          fclose(out);
//          chmod(idxpath, 0600);
//          return 0;
//       }
//       return -1;
//    }
//    char tmp[PATH_MAX];
//    snprintf(tmp, sizeof(tmp), "%s.trunc", idxpath);
//    FILE *out = fopen(tmp, "w");
//    if (!out)
//    {
//       fclose(in);
//       return -1;
//    }
//    char buf[2048];
//    while (fgets(buf, sizeof(buf), in))
//    {
//       if (buf[0] == '#')
//       {
//          /* preserve comments */
//          if (fputs(buf, out) < 0)
//          {
//             fclose(in);
//             fclose(out);
//             unlink(tmp);
//             return -1;
//          }
//          continue;
//       }
//       unsigned long long i = 0, off = 0;
//       char lhex[256] = {0}, chex[256] = {0};
//       if (sscanf(buf, "i=%llu off=%llu lhash=%255s chash=%255s", &i, &off, lhex, chex) == 4)
//       {
//          if (i <= keep_i)
//          {
//             if (fprintf(out, "i=%llu off=%llu lhash=%s chash=%s\n", i, off, lhex, chex) < 0)
//             {
//                fclose(in);
//                fclose(out);
//                unlink(tmp);
//                return -1;
//             }
//          }
//          else
//          {
//             /* reached beyond keep_i -> stop copying further */
//             break;
//          }
//       }
//       else
//       {
//          /* not matching expected format: if keep_i==0 we simply skip */
//       }
//    }
//    fclose(in);
//    fflush(out);
//    fsync(fileno(out));
//    fclose(out);
//    if (rename(tmp, idxpath) != 0)
//    {
//       unlink(tmp);
//       return -1;
//    }
//    chmod(idxpath, 0600);
//    return 0;
// }

// /* ------------ Global state ------------ */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* --- NEW globals for runtime tamper detection --- */

// //-----------------新增
// static time_t g_last_mtime = 0;
// static off_t g_last_size = 0;
// //-----------------新增

// /* signals */
// static void on_signal(int sig)
// {
//    if (sig == SIGINT || sig == SIGTERM)
//       g_run = 0;
// }

// /* ----------------- 新增：读取 idx 到内存，用以查找 trusted prefix ----------------- */
// typedef struct
// {
//    unsigned long long i;
//    unsigned long long off;
//    char lhash[65];
//    char chash[65];
// } IdxEntry;

// typedef struct
// {
//    IdxEntry *entries;
//    size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a)
// {
//    if (!a)
//       return;
//    free(a->entries);
//    a->entries = NULL;
//    a->count = 0;
// }

// /* 从 idx 文件读取到内存（简单 parser），返回 0 成功 */
// static int load_idx_entries(const char *path, IdxArray *out)
// {
//    memset(out, 0, sizeof(*out));
//    FILE *f = fopen(path, "r");
//    if (!f)
//       return -1;
//    char buf[1024];
//    IdxEntry *arr = NULL;
//    size_t cnt = 0, capv = 0;
//    while (fgets(buf, sizeof(buf), f))
//    {
//       if (buf[0] == '#')
//          continue;
//       unsigned long long ii = 0, off = 0;
//       char lhex[128] = {0}, chex[128] = {0};
//       if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4)
//       {
//          if (cnt == capv)
//          {
//             size_t nc = capv ? capv * 2 : 256;
//             IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//             if (!p)
//             {
//                free(arr);
//                fclose(f);
//                return -1;
//             }
//             arr = p;
//             capv = nc;
//          }
//          arr[cnt].i = ii;
//          arr[cnt].off = off;
//          strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash) - 1);
//          strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash) - 1);
//          cnt++;
//       }
//    }
//    fclose(f);
//    out->entries = arr;
//    out->count = cnt;
//    return 0;
// }

// /* 找到可信前缀：返回最后匹配的 idx 行数（0 表示没有匹配），并通过 out_chain/out_off 返回对应二进制 chain 与 偏移（若 matched == 0，chain 置 0，off 置 0）
//    算法：逐行读取 audit.log，在每次读到第 k 行时计算 chain，比较当前 idx 中的 chash（如果存在）。当出现第一次不匹配时（或者 idx 早已没有 k 行）则停止，trusted = k-1。
// */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                                unsigned long long *out_trusted_lines,
//                                unsigned char out_chain[32],
//                                unsigned long long *out_offset)
// {
//    IdxArray idx = {0};
//    if (load_idx_entries(idxpath, &idx) != 0)
//    {
//       /* 无 idx 文件或无法读取 -> no trust */
//       if (out_trusted_lines)
//          *out_trusted_lines = 0;
//       if (out_offset)
//          *out_offset = 0;
//       if (out_chain)
//          memset(out_chain, 0, 32);
//       return -1;
//    }

//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//    {
//       free_idx_array(&idx);
//       return -1;
//    }

//    FILE *lf = fopen(logpath, "rb");
//    if (!lf)
//    {
//       free_idx_array(&idx);
//       return -1;
//    }
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    unsigned char H[32];
//    memset(H, 0, sizeof(H));
//    unsigned long long ln = 0;
//    size_t idx_pos = 0;
//    unsigned long long last_good_i = 0;
//    unsigned long long last_good_off = 0;
//    unsigned char last_good_chain[32];
//    memset(last_good_chain, 0, sizeof(last_good_chain));

//    while ((n = getline(&line, &cap, lf)) != -1)
//    {
//       ln++;
//       size_t normlen = normalize_line(line, (size_t)n);
//       /* update chain: H = SM3(H || normalized_line) */
//       EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//       unsigned int mdlen = 0;
//       if (!ctx)
//       {
//          free(line);
//          fclose(lf);
//          free_idx_array(&idx);
//          return -1;
//       }
//       if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//           EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//           EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//           EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//       {
//          EVP_MD_CTX_free(ctx);
//          free(line);
//          fclose(lf);
//          free_idx_array(&idx);
//          return -1;
//       }
//       EVP_MD_CTX_free(ctx);

//       /* see if idx has an entry for this ln (idx entries might start with i=1 etc) */
//       if (idx_pos < idx.count)
//       {
//          IdxEntry *e = &idx.entries[idx_pos];
//          if (e->i == ln)
//          {
//             /* compare H (hex) with e->chash */
//             char hhex[65];
//             hexdump(H, 32, hhex, sizeof(hhex));
//             if (strcmp(hhex, e->chash) == 0)
//             {
//                /* matched */
//                last_good_i = e->i;
//                last_good_off = e->off + 0;
//                memcpy(last_good_chain, H, 32);
//                idx_pos++;
//                continue;
//             }
//             else
//             {
//                /* mismatch here -> stop: trusted = ln-1 */
//                break;
//             }
//          }
//          else if (e->i < ln)
//          {
//             /* idx has extra earlier entries we already passed - advance idx_pos until >= ln or end */
//             while (idx_pos < idx.count && idx.entries[idx_pos].i < ln)
//                idx_pos++;
//             if (idx_pos < idx.count && idx.entries[idx_pos].i == ln)
//             {
//                char hhex[65];
//                hexdump(H, 32, hhex, sizeof(hhex));
//                if (strcmp(hhex, idx.entries[idx_pos].chash) == 0)
//                {
//                   last_good_i = idx.entries[idx_pos].i;
//                   last_good_off = idx.entries[idx_pos].off;
//                   memcpy(last_good_chain, H, 32);
//                   idx_pos++;
//                   continue;
//                }
//                else
//                {
//                   break;
//                }
//             }
//             else
//             {
//                /* no idx entry for this line -> can't trust beyond previous matched */
//                break;
//             }
//          }
//          else
//          {
//             /* idx_pos points to an entry with i > ln (unlikely normally) -> means idx starts later -> can't trust current ln */
//             break;
//          }
//       }
//       else
//       {
//          /* idx ended -> no more trust */
//          break;
//       }
//    }

//    free(line);
//    fclose(lf);

//    if (out_trusted_lines)
//       *out_trusted_lines = last_good_i;
//    if (out_offset)
//       *out_offset = last_good_off;
//    if (out_chain)
//       memcpy(out_chain, last_good_chain, 32);

//    free_idx_array(&idx);
//    return 0;
// }

// /* ---------- 现有代码：解析 seal 并验证，但当发现链/idx不一致时改为“降级运行”（定位 trusted prefix 并继续） ---------- */
// struct seal
// {
//    char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s)
// {
//    size_t n = strlen(s);
//    while (n && (s[n - 1] == '\n' || s[n - 1] == '\r'))
//       s[--n] = 0;
// }
// static char *trimws(char *s)
// {
//    while (*s == ' ' || *s == '\t')
//       s++;
//    size_t n = strlen(s);
//    while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || s[n - 1] == '\n'))
//       s[--n] = 0;
//    return s;
// }
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//    memset(S, 0, sizeof(*S));
//    FILE *f = fopen(seal_path, "r");
//    if (!f)
//       return -1;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    while ((n = getline(&line, &cap, f)) != -1)
//    {
//       if (line[0] == '#' || line[0] == 0)
//          continue;
//       trimnl(line);
//       char *eq = strchr(line, '=');
//       if (!eq)
//          continue;
//       *eq = 0;
//       char *k = trimws(line);
//       char *v = trimws(eq + 1);
//       if (strcmp(k, "version") == 0)
//          S->version = strdup(v);
//       else if (strcmp(k, "hash") == 0)
//          S->hash = strdup(v);
//       else if (strcmp(k, "sign") == 0)
//          S->sign = strdup(v);
//       else if (strcmp(k, "sm2_id") == 0)
//          S->sm2_id = strdup(v);
//       else if (strcmp(k, "path") == 0)
//          S->path = strdup(v);
//       else if (strcmp(k, "records") == 0)
//          S->records = strdup(v);
//       else if (strcmp(k, "chain") == 0)
//          S->chain = strdup(v);
//       else if (strcmp(k, "key_fpr") == 0)
//          S->key_fpr = strdup(v);
//       else if (strcmp(k, "cert_der_base64") == 0)
//          S->cert_b64 = strdup(v);
//       else if (strcmp(k, "sig_base64") == 0)
//          S->sig_b64 = strdup(v);
//       else if (strcmp(k, "index_sm3") == 0)
//          S->index_sm3 = strdup(v);
//    }
//    free(line);
//    fclose(f);
//    if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//        S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//       return 0;
//    return -1;
// }
// static void free_seal(struct seal *S)
// {
//    free(S->version);
//    free(S->hash);
//    free(S->sign);
//    free(S->sm2_id);
//    free(S->path);
//    free(S->records);
//    free(S->chain);
//    free(S->key_fpr);
//    free(S->index_sm3);
//    free(S->cert_b64);
//    free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//    char *buf = NULL;
//    if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0)
//       return NULL;
//    return buf;
// }

// /* 计算前 N 行链 */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;
//    unsigned char H[32] = {0};
//    unsigned long long scanned = 0;
//    FILE *fp = fopen(path, "rb");
//    if (!fp)
//       return -1;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//    {
//       size_t normlen = normalize_line(line, (size_t)n);
//       EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//       if (!ctx)
//       {
//          fclose(fp);
//          free(line);
//          return -1;
//       }
//       int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                 EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                 EVP_DigestUpdate(ctx, line, normlen) == 1);
//       unsigned int mdlen = 0;
//       if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//       {
//          EVP_MD_CTX_free(ctx);
//          fclose(fp);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(ctx);
//       scanned++;
//    }
//    free(line);
//    fclose(fp);
//    memcpy(out32, H, 32);
//    return 0;
// }

// /* 验证现有seal，若存在。
//    行为调整：
//     - 如果没有 seal: 返回 1（first run，允许初始化）
//     - 如果 seal 存在且一致: 返回 0
//     - 如果 seal 存在但 chain/index 与文件不一致:  *不会直接失败/退出*
//           而是定位可信前缀（trusted lines）并将全局状态设置到 trusted 前缀，同时**裁剪 idx 到 trusted 前缀**，返回 0（继续运行，但已告警）
// */
// static int verify_existing_seal(void)
// {
//    if (access(SEAL_PATH, R_OK) != 0)
//    {
//       log_msg("no existing seal: treat as first-run");
//       return 1; /* no seal present, allow init from scratch */
//    }
//    struct seal S;
//    if (parse_seal_file(SEAL_PATH, &S) != 0)
//    {
//       log_msg("parse existing seal failed");
//       trigger_alarm("Existing seal is not parseable - manual recovery needed");
//       return -1;
//    }
//    if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0 || strcmp(S.sm2_id, SM2_ID) != 0)
//    {
//       log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//       trigger_alarm("Existing seal algorithm mismatch");
//       free_seal(&S);
//       return -1;
//    }
//    /* verify signature */
//    size_t cert_len = 0;
//    unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//    if (!cert_der)
//    {
//       free_seal(&S);
//       trigger_alarm("Existing seal cert b64 decode failed");
//       return -1;
//    }
//    const unsigned char *p = cert_der;
//    X509 *cert = d2i_X509(NULL, &p, cert_len);
//    free(cert_der);
//    if (!cert)
//    {
//       free_seal(&S);
//       trigger_alarm("Existing seal d2i_X509 failed");
//       return -1;
//    }
//    EVP_PKEY *pub = X509_get_pubkey(cert);
//    if (!pub)
//    {
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal get pubkey failed");
//       return -1;
//    }
//    EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//    if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal cert is not SM2");
//       return -1;
//    }

//    /* fingerprint check */
//    unsigned char *der2 = NULL;
//    int der2len = i2d_X509(cert, &der2);
//    if (der2len <= 0)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal i2d_X509 failed");
//       return -1;
//    }
//    unsigned char mdv[EVP_MAX_MD_SIZE];
//    unsigned int mdlen = 0;
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//        EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//        EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//    {
//       if (ctx)
//          EVP_MD_CTX_free(ctx);
//       OPENSSL_free(der2);
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal fingerprint digest failed");
//       return -1;
//    }
//    EVP_MD_CTX_free(ctx);
//    OPENSSL_free(der2);
//    char fpr_hex[65];
//    hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//    if (strcmp(fpr_hex, S.key_fpr) != 0)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal certificate fingerprint mismatch");
//       return -1;
//    }
//    /* verify signature */
//    char *canon = build_canon_from_seal(&S);
//    if (!canon)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("asprintf canon failed");
//       return -1;
//    }
//    size_t siglen = 0;
//    unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//    if (!sig)
//    {
//       free(canon);
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal sig b64 decode failed");
//       return -1;
//    }
//    int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//    free(sig);
//    free(canon);
//    if (v != 1)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal signature invalid");
//       return -1;
//    }

//    /* chain check: recompute chain on log first N lines */
//    unsigned long long N = strtoull(S.records, NULL, 10);
//    if (N > 0)
//    {
//       unsigned char chainR[32];
//       if (sm3_chain_file_limit_local(S.path, N, chainR) != 0)
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Cannot compute chain from audit.log for existing seal");
//          return -1;
//       }
//       char chx[65];
//       hexdump(chainR, 32, chx, sizeof(chx));
//       if (strcmp(chx, S.chain) != 0)
//       {
//          /* 链不匹配 -> 进入降级处理：尝试定位 trusted prefix 并继续运行 */
//          log_msg("Existing seal chain mismatch with current audit.log; attempting to compute trusted prefix and continue in degraded mode");
//          trigger_alarm("Existing seal chain mismatch with audit.log; degraded mode (continue) - manual review required");

//          unsigned long long trusted_lines = 0;
//          unsigned char trusted_chain[32];
//          unsigned long long trusted_off = 0;
//          memset(trusted_chain, 0, sizeof(trusted_chain));
//          int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//          if (ret != 0)
//          {
//             log_msg("find_trusted_prefix failed - no trusted prefix found; set trusted_lines=0 and continue (manual intervention required)");
//             /* truncate idx to 0 */
//             if (truncate_idx_to_i(IDX_PATH, 0) != 0)
//                log_msg("truncate idx to 0 failed");
//             memset(g_chain, 0, sizeof(g_chain));
//             g_lines = 0;
//             g_offset = 0;
//          }
//          else
//          {
//             /* truncate idx to trusted_lines */
//             if (truncate_idx_to_i(IDX_PATH, trusted_lines) != 0)
//                log_msg("truncate idx to trusted_lines=%llu failed", trusted_lines);
//             memcpy(g_chain, trusted_chain, 32);
//             g_lines = trusted_lines;
//             g_offset = trusted_off;
//             log_msg("Trusted prefix located: lines=%llu offset=%llu", g_lines, g_offset);
//          }
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          return 0;
//       }
//    }
//    else
//    {
//       /* records=0, ok */
//    }

//    /* idx check */
//    if (access(IDX_PATH, R_OK) == 0)
//    {
//       char now_sm3[65];
//       if (sm3_file_hex(IDX_PATH, now_sm3) != 0)
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Cannot compute idx sm3 for existing seal");
//          return -1;
//       }
//       if (strcmp(now_sm3, S.index_sm3) != 0)
//       {
//          /* idx 被修改，报警并尝试定位可信前缀，同上 */
//          log_msg("Existing seal index_sm3 mismatch - idx file changed; attempting to find trusted prefix and continue");
//          trigger_alarm("Index file hash mismatch; degraded mode (continue) - manual review required");

//          unsigned long long trusted_lines = 0;
//          unsigned char trusted_chain[32];
//          unsigned long long trusted_off = 0;
//          memset(trusted_chain, 0, sizeof(trusted_chain));
//          int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//          if (ret != 0)
//          {
//             log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//             if (truncate_idx_to_i(IDX_PATH, 0) != 0)
//                log_msg("truncate idx to 0 failed");
//             memset(g_chain, 0, sizeof(g_chain));
//             g_lines = 0;
//             g_offset = 0;
//          }
//          else
//          {
//             if (truncate_idx_to_i(IDX_PATH, trusted_lines) != 0)
//                log_msg("truncate idx to trusted_lines=%llu failed", trusted_lines);
//             memcpy(g_chain, trusted_chain, 32);
//             g_lines = trusted_lines;
//             g_offset = trusted_off;
//             log_msg("Trusted prefix located after idx mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//          }
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          return 0;
//       }
//       /* tail check */
//       FILE *ix = fopen(IDX_PATH, "r");
//       if (ix)
//       {
//          char buf[1024];
//          unsigned long long last_i = 0;
//          char last_ch[65] = {0};
//          while (fgets(buf, sizeof(buf), ix))
//          {
//             if (buf[0] == '#')
//                continue;
//             unsigned long long i = 0, off = 0;
//             char l[128] = {0}, c[128] = {0};
//             if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, l, c) == 4)
//             {
//                last_i = i;
//                strncpy(last_ch, c, sizeof(last_ch) - 1);
//             }
//          }
//          fclose(ix);
//          if (last_i != N || strcmp(last_ch, S.chain) != 0)
//          {
//             log_msg("Existing seal vs idx tail mismatch; try to find trusted prefix and continue");
//             trigger_alarm("Seal vs idx tail mismatch; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32];
//             unsigned long long trusted_off = 0;
//             memset(trusted_chain, 0, sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0)
//             {
//                log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                if (truncate_idx_to_i(IDX_PATH, 0) != 0)
//                   log_msg("truncate idx to 0 failed");
//                memset(g_chain, 0, sizeof(g_chain));
//                g_lines = 0;
//                g_offset = 0;
//             }
//             else
//             {
//                if (truncate_idx_to_i(IDX_PATH, trusted_lines) != 0)
//                   log_msg("truncate idx to trusted_lines=%llu failed", trusted_lines);
//                memcpy(g_chain, trusted_chain, 32);
//                g_lines = trusted_lines;
//                g_offset = trusted_off;
//                log_msg("Trusted prefix located after idx tail mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             return 0;
//          }
//       }
//       else
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Cannot open idx for existing seal");
//          return -1;
//       }
//    }
//    else
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("No idx file but existing seal expects one");
//       return -1;
//    }

//    /* parse chain into g_chain */
//    memset(g_chain, 0, 32);
//    for (int i = 0; i < 32; i++)
//    {
//       unsigned int hi, lo;
//       if (sscanf(&S.chain[i * 2], "%1x%1x", &hi, &lo) != 2)
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Existing seal chain hex parse error");
//          return -1;
//       }
//       g_chain[i] = (hi << 4) | lo;
//    }
//    g_lines = strtoull(S.records, NULL, 10);

//    /* compute offset from idx last off */
//    FILE *ix = fopen(IDX_PATH, "r");
//    if (!ix)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Cannot open idx for reading offset");
//       return -1;
//    }
//    char buf[1024];
//    unsigned long long last_off = 0;
//    unsigned long long last_i = 0;
//    while (fgets(buf, sizeof(buf), ix))
//    {
//       if (buf[0] == '#')
//          continue;
//       unsigned long long i = 0, off = 0;
//       char lhex[128] = {0}, chex[128] = {0};
//       if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//       {
//          last_i = i;
//          last_off = off;
//       }
//    }
//    fclose(ix);
//    if (last_i != g_lines)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing idx last_i != seal records - inconsistent");
//       return -1;
//    }
//    g_offset = last_off;

//    EVP_PKEY_free(pub);
//    X509_free(cert);
//    free_seal(&S);
//    return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;

//    size_t normlen = normalize_line(line_buf, raw_n);
//    unsigned char lhash[32], newH[32];
//    unsigned int lm = 0, hm = 0;
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    if (!ctx)
//       return -1;
//    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//        EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//        EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//    {
//       EVP_MD_CTX_free(ctx);
//       return -1;
//    }
//    EVP_MD_CTX_free(ctx);

//    EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//    if (!ctx2)
//       return -1;
//    if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//        EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//        EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//        EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//    {
//       EVP_MD_CTX_free(ctx2);
//       return -1;
//    }
//    EVP_MD_CTX_free(ctx2);

//    memcpy(g_chain, newH, 32);
//    g_lines++;

//    char lhex[65], chex[65];
//    hexdump(lhash, 32, lhex, sizeof(lhex));
//    hexdump(g_chain, 32, chex, sizeof(chex));

//    if (!g_idx)
//       return -1;
//    int fd = fileno(g_idx);
//    if (fd < 0)
//       return -1;

//    if (flock(fd, LOCK_EX) != 0)
//       log_msg("flock idx EX failed: %s", strerror(errno));
//    fseek(g_idx, 0, SEEK_END);
//    if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//                (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//    {
//       log_msg("failed write idx line: %s", strerror(errno));
//       flock(fd, LOCK_UN);
//       return -1;
//    }
//    fflush(g_idx);
//    fsync(fd);
//    flock(fd, LOCK_UN);

//    g_offset += (unsigned long long)raw_n;
//    return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//    //-----------------新增
//    unsigned long long now_lines = count_file_lines_simple(LOG_PATH);
//    if (now_lines < g_lines)
//    {
//       log_msg("seal guard: audit.log lines(%llu) < in-memory lines(%llu); recovering", now_lines, g_lines);
//       unsigned long long trusted_lines = 0, trusted_off = 0;
//       unsigned char trusted_chain[32] = {0};
//       if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0)
//       {
//          truncate_idx_to_i(IDX_PATH, trusted_lines);
//          memcpy(g_chain, trusted_chain, 32);
//          g_lines = trusted_lines;
//          g_offset = trusted_off;
//          if (g_idx)
//          {
//             fclose(g_idx);
//             g_idx = NULL;
//          }
//          g_idx = fopen(IDX_PATH, "a+");
//       }
//       else
//       {
//          truncate_idx_to_i(IDX_PATH, 0);
//          memset(g_chain, 0, sizeof(g_chain));
//          g_lines = 0;
//          g_offset = (unsigned long long)now_lines;
//       }
//    }
//    EVP_PKEY *pkey = NULL;
//    X509 *cert = NULL;
//    //-----------------新增

//    /* read pass */
//    FILE *pf = fopen(PASS_PATH, "r");
//    if (!pf)
//    {
//       log_msg("open PASS_PATH failed");
//       return -1;
//    }
//    char *secret = NULL;
//    size_t cap = 0;
//    ssize_t n = getline(&secret, &cap, pf);
//    fclose(pf);
//    if (n <= 0)
//    {
//       free(secret);
//       log_msg("read PASS_PATH empty");
//       return -1;
//    }
//    while (n && (secret[n - 1] == '\n' || secret[n - 1] == '\r'))
//       secret[--n] = 0;

//    /* read salt */
//    unsigned char salt[SALT_BYTES];
//    FILE *sf = fopen(SALT_PATH, "r");
//    if (!sf)
//    {
//       log_msg("open SALT_PATH failed");
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    char saltbuf[256];
//    if (!fgets(saltbuf, sizeof(saltbuf), sf))
//    {
//       fclose(sf);
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    fclose(sf);
//    size_t slen = strlen(saltbuf);
//    while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//       saltbuf[--slen] = 0;
//    if (slen != (size_t)SALT_BYTES * 2)
//    {
//       log_msg("salt length error");
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    for (size_t i = 0; i < SALT_BYTES; i++)
//    {
//       unsigned int hi, lo;
//       if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//       {
//          OPENSSL_cleanse(secret, strlen(secret));
//          free(secret);
//          return -1;
//       }
//       salt[i] = (hi << 4) | lo;
//    }

//    unsigned char derived[DERIVE_BYTES];
//    if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                           EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//    {
//       log_msg("PBKDF2-SM3 failed");
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    char passhex[DERIVE_BYTES * 2 + 1];
//    hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//    OPENSSL_cleanse(secret, strlen(secret));
//    free(secret);

//    /* load p12 */
//    FILE *pf2 = fopen(P12_PATH, "rb");
//    if (!pf2)
//    {
//       log_msg("open P12_PATH failed");
//       return -1;
//    }
//    PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//    fclose(pf2);
//    if (!p12)
//    {
//       log_msg("d2i_PKCS12_fp failed");
//       return -1;
//    }
//    if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//    {
//       log_msg("PKCS12_parse failed");
//       PKCS12_free(p12);
//       return -1;
//    }
//    PKCS12_free(p12);

//    /* fingerprint */
//    unsigned char *der = NULL;
//    int derlen = i2d_X509(cert, &der);
//    if (derlen <= 0)
//    {
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    unsigned char mdv[EVP_MAX_MD_SIZE];
//    unsigned int mdlen = 0;
//    if (!ctx ||
//        EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//        EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//        EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//    {
//       if (ctx)
//          EVP_MD_CTX_free(ctx);
//       OPENSSL_free(der);
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }
//    EVP_MD_CTX_free(ctx);
//    OPENSSL_free(der);
//    char fpr_hex[65];
//    hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//    char chain_hex[65];
//    hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//    char idx_sm3[65] = {0};
//    if (sm3_file_hex(IDX_PATH, idx_sm3) != 0)
//    {
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       log_msg("sm3 idx failed");
//       return -1;
//    }

//    /* canonical */
//    char *canon = NULL;
//    if (asprintf(&canon,
//                 "version=1\n"
//                 "hash=sm3\n"
//                 "sign=sm2\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%llu\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//    {
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }

//    /* sign */
//    unsigned char *sig = NULL;
//    size_t siglen = 0;
//    if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0)
//    {
//       log_msg("gm_sm2_sign failed");
//       free(canon);
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }
//    char *sigb64 = b64_encode(sig, siglen);
//    OPENSSL_free(sig);

//    /* certb64 */
//    unsigned char *der2 = NULL;
//    int der2len = i2d_X509(cert, &der2);
//    char *certb64 = NULL;
//    if (der2len > 0)
//    {
//       certb64 = b64_encode(der2, der2len);
//       OPENSSL_free(der2);
//    }
//    else
//    {
//       log_msg("i2d_X509 failed");
//       free(canon);
//       free(sigb64);
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }
//    EVP_PKEY_free(pkey);
//    X509_free(cert);

//    /* atomic write seal */
//    char tmp[PATH_MAX];
//    snprintf(tmp, sizeof(tmp), "%s.tmp", SEAL_PATH);
//    FILE *sfw = fopen(tmp, "w");
//    if (!sfw)
//    {
//       log_msg("open seal tmp failed: %s", strerror(errno));
//       free(canon);
//       free(sigb64);
//       free(certb64);
//       return -1;
//    }
//    fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//    fprintf(sfw, "cert_der_base64=%s\n", certb64);
//    fprintf(sfw, "sig_base64=%s\n", sigb64);
//    fclose(sfw);
//    chmod(tmp, 0640);
//    if (rename(tmp, SEAL_PATH) != 0)
//    {
//       log_msg("rename seal tmp failed: %s", strerror(errno));
//       unlink(tmp);
//       free(canon);
//       free(sigb64);
//       free(certb64);
//       return -1;
//    }
//    free(canon);
//    free(sigb64);
//    free(certb64);
//    log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//    return 0;
// }

// /* 初始化：调整顺序：先验证 seal（可能会修改 g_chain/g_lines/g_offset，或裁剪 idx），
//    然后再打开 idx 以 append 模式作为后续写入使用 */
// static int gmseal_init(void)
// {
//    memset(g_chain, 0, sizeof(g_chain));
//    g_lines = 0;
//    g_offset = 0;

//    /* 先验证/恢复现有 seal（可能会做降级并裁剪 idx） */
//    int vs = verify_existing_seal();
//    if (vs < 0)
//    {
//       log_msg("Existing seal mismatch with audit.log / idx. Aborting.");
//       return -1;
//    }
//    if (vs > 0)
//    {
//       /* 没有 seal，首次启动：从 log 重构 idx（允许） */
//       unsigned char new_chain[32];
//       unsigned long long new_lines = 0, new_off = 0;
//       if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0)
//       {
//          log_msg("rebuild_idx_from_log failed (first run)");
//          return -1;
//       }
//       memcpy(g_chain, new_chain, 32);
//       g_lines = new_lines;
//       g_offset = new_off;
//       log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//    }
//    else
//    {
//       /* seal 验证成功或已在 verify_existing_seal 中设置 trusted prefix 状态 */
//       log_msg("existing seal verified or degraded-mode prepared. state lines=%llu off=%llu", g_lines, g_offset);
//    }

//    /* 打开/创建 idx 文件用于后续 append（在 verify/重建后打开，确保文件已被裁剪或重建） */
//    g_idx = fopen(IDX_PATH, "a+");
//    if (!g_idx)
//    {
//       log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//       return -1;
//    }
//    chmod(IDX_PATH, 0600);

//    return 0;
// }

// /* Tail: 当检测到截断/inode 改变等问题时，进入“尝试恢复 trusted prefix 并继续”而非直接 exit */
// static int tail_and_process(void)
// {
//    FILE *lf = NULL;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;

//    for (;;)
//    {
//       struct stat st;
//       if (stat(LOG_PATH, &st) != 0)
//       {
//          sleep(1);
//          if (!g_run)
//             break;
//          continue;
//       }
//       //----------------新增
//       if (g_last_mtime == 0)
//       {
//          g_last_mtime = st.st_mtime;
//          g_last_size = st.st_size;
//       }
//       else
//       {
//          if (st.st_mtime != g_last_mtime && (unsigned long long)st.st_size <= g_offset)
//          {
//             log_msg("mtime changed but size<=offset -> check prefix");
//             unsigned char tmp_chain[32];
//             if (g_lines > 0 && sm3_chain_file_limit_local(LOG_PATH, g_lines, tmp_chain) == 0)
//             {
//                if (memcmp(tmp_chain, g_chain, 32) != 0)
//                {
//                   log_msg("prefix mismatch -> recover trusted prefix");
//                   trigger_alarm("Audit log modified in-place; recovering");
//                   unsigned long long trusted_lines = 0, trusted_off = 0;
//                   unsigned char trusted_chain[32] = {0};
//                   if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0)
//                   {
//                      truncate_idx_to_i(IDX_PATH, trusted_lines);
//                      memcpy(g_chain, trusted_chain, 32);
//                      g_lines = trusted_lines;
//                      g_offset = trusted_off;
//                      if (g_idx)
//                      {
//                         fclose(g_idx);
//                         g_idx = NULL;
//                      }
//                      g_idx = fopen(IDX_PATH, "a+");
//                      if (lf)
//                      {
//                         fclose(lf);
//                         lf = NULL;
//                      }
//                      lf = fopen(LOG_PATH, "rb");
//                      if (lf)
//                         fseeko(lf, (off_t)g_offset, SEEK_SET);
//                   }
//                   else
//                   {
//                      truncate_idx_to_i(IDX_PATH, 0);
//                      memset(g_chain, 0, sizeof(g_chain));
//                      g_lines = 0;
//                      g_offset = (unsigned long long)st.st_size;
//                   }
//                }
//             }
//          }
//          g_last_mtime = st.st_mtime;
//          g_last_size = st.st_size;
//       }
// //----------------新增
//          if (!lf)
//          {
//             lf = fopen(LOG_PATH, "rb");
//             if (!lf)
//             {
//                sleep(1);
//                continue;
//             }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0)
//             {
//                /* 不再直接 exit，而是尝试重新定位 trusted prefix */
//                log_msg("fseeko to %llu failed: attempt to recover trusted prefix", g_offset);
//                trigger_alarm("Cannot seek to last known offset in audit.log; attempting recovery and continuing");
//                /* try find trusted prefix using idx and log */
//                unsigned long long trusted_lines = 0;
//                unsigned char trusted_chain[32];
//                unsigned long long trusted_off = 0;
//                memset(trusted_chain, 0, sizeof(trusted_chain));
//                if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0)
//                {
//                   /* truncate idx to trusted_lines to keep consistency */
//                   if (truncate_idx_to_i(IDX_PATH, trusted_lines) != 0)
//                   {
//                      log_msg("truncate idx to trusted_lines=%llu failed during recovery", trusted_lines);
//                   }
//                   memcpy(g_chain, trusted_chain, 32);
//                   g_lines = trusted_lines;
//                   g_offset = trusted_off;
//                   log_msg("Recovery: set trusted state lines=%llu off=%llu and reopen", g_lines, g_offset);
//                   /* reopen lf and seek again */
//                   fclose(lf);
//                   lf = fopen(LOG_PATH, "rb");
//                   if (!lf)
//                   {
//                      log_msg("reopen log failed after recovery");
//                      return -1;
//                   }
//                   if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0)
//                   {
//                      log_msg("seek failed even after recovery; set to file end and continue");
//                      fseeko(lf, 0, SEEK_END);
//                      g_offset = (unsigned long long)ftello(lf);
//                   }
//                   /* reopen g_idx so it points to newly truncated idx tail */
//                   if (g_idx)
//                   {
//                      fclose(g_idx);
//                      g_idx = NULL;
//                   }
//                   g_idx = fopen(IDX_PATH, "a+");
//                   if (!g_idx)
//                      log_msg("reopen idx after recovery failed");
//                }
//                else
//                {
//                   log_msg("Recovery attempt failed: no trusted prefix found; seeking to file end and continue");
//                   fclose(lf);
//                   lf = fopen(LOG_PATH, "rb");
//                   if (!lf)
//                   {
//                      sleep(1);
//                      continue;
//                   }
//                   fseeko(lf, 0, SEEK_END);
//                   g_offset = (unsigned long long)ftello(lf);
//                }
//             }
//          }

//          while ((n = getline(&line, &cap, lf)) != -1)
//          {
//             if (n > 0)
//             {
//                if (gmseal_on_line(line, (size_t)n) != 0)
//                {
//                   log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                   trigger_alarm("Hashing error when processing new audit line; exit");
//                   free(line);
//                   line = NULL;
//                   fclose(lf);
//                   lf = NULL;
//                   return -1;
//                }
//                if (ALWAYS_REFRESH_SEAL)
//                {
//                   if (gmseal_write_seal() != 0)
//                      log_msg("gmseal_write_seal failed");
//                }
//             }
//             if (!g_run)
//                break;
//          }

//          if (!g_run)
//             break;

//          /* EOF：检查是否截断或 inode 改变，遇到则报警并尝试恢复（而非直接退出） */
//          clearerr(lf);
//          struct stat st2;
//          if (stat(LOG_PATH, &st2) != 0)
//          {
//             fclose(lf);
//             lf = NULL;
//             sleep(1);
//             continue;
//          }
//          if ((unsigned long long)st2.st_size < g_offset)
//          {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> attempt recovery and continue",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; attempting recovery and continue (manual review required)");
//             /* attempt to locate trusted prefix and set g_offset appropriately */
//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32];
//             unsigned long long trusted_off = 0;
//             memset(trusted_chain, 0, sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0)
//             {
//                if (truncate_idx_to_i(IDX_PATH, trusted_lines) != 0)
//                   log_msg("truncate idx to trusted_lines failed after truncation detection");
//                memcpy(g_chain, trusted_chain, 32);
//                g_lines = trusted_lines;
//                g_offset = trusted_off;
//                log_msg("Recovery after truncation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//                if (g_idx)
//                {
//                   fclose(g_idx);
//                   g_idx = NULL;
//                }
//                g_idx = fopen(IDX_PATH, "a+");
//                if (!g_idx)
//                   log_msg("reopen idx after truncation failed");
//             }
//             else
//             {
//                log_msg("Recovery after truncation failed: no trusted prefix; setting g_offset to file end");
//                g_offset = (unsigned long long)st2.st_size;
//                memset(g_chain, 0, sizeof(g_chain));
//                g_lines = 0;
//                if (truncate_idx_to_i(IDX_PATH, 0) != 0)
//                   log_msg("truncate idx to 0 failed after truncation detection");
//                if (g_idx)
//                {
//                   fclose(g_idx);
//                   g_idx = NULL;
//                }
//                g_idx = fopen(IDX_PATH, "a+");
//                if (!g_idx)
//                   log_msg("reopen idx after truncation (to zero) failed");
//             }
//             /* reposition lf for next loop */
//             fclose(lf);
//             lf = NULL;
//             continue;
//          }
//          if (st2.st_ino != st.st_ino)
//          {
//             log_msg("audit.log inode changed (rotation) -> attempt recovery and continue");
//             trigger_alarm("Audit log rotated; attempting recovery and continue (manual review required)");
//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32];
//             unsigned long long trusted_off = 0;
//             memset(trusted_chain, 0, sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0)
//             {
//                if (truncate_idx_to_i(IDX_PATH, trusted_lines) != 0)
//                   log_msg("truncate idx to trusted_lines failed after rotation");
//                memcpy(g_chain, trusted_chain, 32);
//                g_lines = trusted_lines;
//                g_offset = trusted_off;
//                log_msg("Recovery after rotation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//                if (g_idx)
//                {
//                   fclose(g_idx);
//                   g_idx = NULL;
//                }
//                g_idx = fopen(IDX_PATH, "a+");
//                if (!g_idx)
//                   log_msg("reopen idx after rotation failed");
//             }
//             else
//             {
//                /* fallback: start from file end */
//                log_msg("Recovery after rotation failed: no trusted prefix; set g_offset to file end");
//                g_offset = (unsigned long long)st2.st_size;
//                memset(g_chain, 0, sizeof(g_chain));
//                g_lines = 0;
//                if (truncate_idx_to_i(IDX_PATH, 0) != 0)
//                   log_msg("truncate idx to 0 failed after rotation");
//                if (g_idx)
//                {
//                   fclose(g_idx);
//                   g_idx = NULL;
//                }
//                g_idx = fopen(IDX_PATH, "a+");
//                if (!g_idx)
//                   log_msg("reopen idx after rotation (to zero) failed");
//             }
//             fclose(lf);
//             lf = NULL;
//             continue;
//          }
//          usleep(100000);
//       }

//       if (line)
//          free(line);
//       if (lf)
//          fclose(lf);
//       return 0;
//    }

// int main(int argc, char **argv)
// {
//    OpenSSL_add_all_algorithms();
//    ERR_load_crypto_strings();

//    log_open();
//    log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//    if (gm_crypto_init() != 0)
//    {
//       log_msg("gm_crypto_init failed");
//       fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//       return 2;
//    }

//    signal(SIGINT, on_signal);
//    signal(SIGTERM, on_signal);

//    if (gmseal_init() != 0)
//    {
//       log_msg("gmseal_init failed (abort)");
//       gm_crypto_cleanup();
//       if (g_log)
//          fclose(g_log);
//       return 2;
//    }

//    /* initial seal */
//    if (gmseal_write_seal() != 0)
//    {
//       log_msg("initial seal write failed");
//    }

//    /* main tail loop */
//    if (tail_and_process() != 0)
//    {
//       log_msg("tail_and_process aborted due to detected issues");
//    }

//    /* normal exit */
//    if (gmseal_write_seal() != 0)
//       log_msg("final seal write failed");
//    if (g_idx)
//       fclose(g_idx);
//    gm_crypto_cleanup();
//    if (g_log)
//       fclose(g_log);
//    EVP_cleanup();
//    ERR_free_strings();
//    log_msg("audisp-gmseal exited");
//    return 0;
// }



//版本暂用（idx与seal   和  audit.log差一行内容  导致后续审计报错 ）

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"     /* trigger_alarm */
// #include "../../lib/gm_crypto.h"   /* gm_sm2_sign, gm_sm2_verify, gm_crypto_init etc. */

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* base64 decode (no newlines) */
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* rebuild idx: 提供工具函数，但本修复版仅在首次无seal时用于初始化 */
// static int rebuild_idx_from_log(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, offset = 0;
//     unsigned char H[32] = {0};

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0) {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* ------------ Global state ------------ */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static FILE *g_idx = NULL;
// static int g_run = 1;

// /* signals */
// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* ----------------- 新增：读取 idx 到内存，用以查找 trusted prefix ----------------- */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL;
//     a->count = 0;
// }

// /* 从 idx 文件读取到内存（简单 parser），返回 0 成功 */
// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out, 0, sizeof(*out));
//     FILE *f = fopen(path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     /* 先收集条目到动态数组 */
//     IdxEntry *arr = NULL;
//     size_t cnt = 0, capv = 0;
//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii;
//             arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr;
//     out->count = cnt;
//     return 0;
// }

// /* 找到可信前缀：返回最后匹配的 idx 行数（0 表示没有匹配），并通过 out_chain/out_off 返回对应二进制 chain 与 偏移（若 matched == 0，chain 置 0，off 置 0）
//    算法：逐行读取 audit.log，在每次读到第 k 行时计算 chain，比较当前 idx 中的 chash（如果存在）。当出现第一次不匹配时（或者 idx 早已没有 k 行）则停止，trusted = k-1。
// */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         /* 无 idx 文件或无法读取 -> no trust */
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_offset) *out_offset = 0;
//         if (out_chain) memset(out_chain, 0, 32);
//         return -1;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32];
//     memset(H, 0, sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0;
//     unsigned long long last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         /* update chain: H = SM3(H || normalized_line) */
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned int mdlen = 0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         /* see if idx has an entry for this ln (idx entries might start with i=1 etc) */
//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 /* compare H (hex) with e->chash */
//                 char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     /* matched */
//                     last_good_i = e->i;
//                     last_good_off = e->off + 0;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else {
//                     /* mismatch here -> stop: trusted = ln-1 */
//                     break;
//                 }
//             } else if (e->i < ln) {
//                 /* idx has extra earlier entries we already passed - advance idx_pos until >= ln or end */
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else {
//                         break;
//                     }
//                 } else {
//                     /* no idx entry for this line -> can't trust beyond previous matched */
//                     break;
//                 }
//             } else {
//                 /* idx_pos points to an entry with i > ln (unlikely normally) -> means idx starts later -> can't trust current ln */
//                 break;
//             }
//         } else {
//             /* idx ended -> no more trust */
//             break;
//         }
//     }

//     free(line);
//     fclose(lf);

//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);

//     free_idx_array(&idx);
//     return 0;
// }

// /* ---------- 现有代码：解析 seal 并验证，但当发现链/idx不一致时改为“降级运行”（定位 trusted prefix 并继续） ---------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k=trimws(line);
//         char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// /* 计算前 N 行链 */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//     {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen=0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     return 0;
// }

// /* 验证现有seal，若存在。
//    行为调整：
//     - 如果没有 seal: 返回 1（first run，允许初始化）
//     - 如果 seal 存在且一致: 返回 0
//     - 如果 seal 存在但 chain/index 与文件不一致:  *不会直接失败/退出*
//           而是定位可信前缀（trusted lines）并将全局状态设置到 trusted 前缀，返回 0（继续运行，但已告警）
// */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present, allow init from scratch */
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }
//     /* verify signature */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }
//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     /* chain check: recompute chain on log first N lines */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             /* 链不匹配 -> 进入降级处理：尝试定位 trusted prefix 并继续运行 */
//             log_msg("Existing seal chain mismatch with current audit.log; attempting to compute trusted prefix and continue in degraded mode");
//             trigger_alarm("Existing seal chain mismatch with audit.log; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix found; set trusted_lines=0 and continue (manual intervention required)");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//     } else {
//         /* records=0, ok */
//     }

//     /* idx check */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             /* idx 被修改，报警并尝试定位可信前缀，同上 */
//             log_msg("Existing seal index_sm3 mismatch - idx file changed; attempting to find trusted prefix and continue");
//             trigger_alarm("Index file hash mismatch; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 0) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//             } else {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Trusted prefix located after idx mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//         /* tail check */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65]={0};
//             while (fgets(buf, sizeof(buf), ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("Existing seal vs idx tail mismatch; try to find trusted prefix and continue");
//                 trigger_alarm("Seal vs idx tail mismatch; degraded mode (continue) - manual review required");

//                 unsigned long long trusted_lines = 0;
//                 unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//                 if (ret != 0) {
//                     log_msg("find_trusted_prefix failed - no trusted prefix; set to 0");
//                     memset(g_chain,0,sizeof(g_chain));
//                     g_lines = 0;
//                     g_offset = 0;
//                 } else {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Trusted prefix located after idx tail mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//                 }
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* parse chain into g_chain */
//     memset(g_chain,0,32);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi,&lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i]= (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx for reading offset");
//         return -1;
//     }
//     char buf[1024]; unsigned long long last_off=0; unsigned long long last_i=0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i=i; last_off=off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (!g_idx) return -1;
//     int fd = fileno(g_idx);
//     if (fd < 0) return -1;

//     if (flock(fd, LOCK_EX) != 0)
//         log_msg("flock idx EX failed: %s", strerror(errno));
//     fseek(g_idx, 0, SEEK_END);
//     if (fprintf(g_idx, "i=%llu off=%llu lhash=%s chash=%s\n",
//             (unsigned long long)g_lines, (unsigned long long)g_offset, lhex, chex) < 0)
//     {
//         log_msg("failed write idx line: %s", strerror(errno));
//         flock(fd, LOCK_UN);
//         return -1;
//     }
//     fflush(g_idx);
//     fsync(fd);
//     flock(fd, LOCK_UN);

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if (n<=0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n] = 0;

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) {
//         unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//         salt[i] = (hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                            EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx ||
//         EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx) EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1;
//     }

//     /* canonical */
//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     /* sign */
//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* certb64 */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len > 0) {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 初始化：不再自动重建 idx；如有 existing seal 必须严格匹配或进入降级模式（trusted prefix） */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     /* 打开/创建 idx 文件 */
//     g_idx = fopen(IDX_PATH, "a+");
//     if (!g_idx) {
//         log_msg("open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     chmod(IDX_PATH, 0600);

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch with audit.log / idx. Aborting.");
//         return -1;
//     }
//     if (vs > 0) {
//         /* 没有 seal，首次启动：从 log 重构 idx（允许） */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log failed (first run)");
//             return -1;
//         }
//         /* 重新打开 idx 指向新文件 */
//         if (g_idx) { fclose(g_idx); g_idx=NULL; }
//         g_idx = fopen(IDX_PATH, "a+");
//         if (!g_idx) {
//             log_msg("open %s failed after rebuild: %s", IDX_PATH, strerror(errno));
//             return -1;
//         }
//         chmod(IDX_PATH, 0600);
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         /* seal 验证成功或已在 verify_existing_seal 中设置 trusted prefix 状态 */
//         log_msg("existing seal verified or degraded-mode prepared. state lines=%llu off=%llu", g_lines, g_offset);
//     }
//     return 0;
// }

// /* Tail: 当检测到截断/inode 改变等问题时，进入“尝试恢复 trusted prefix 并继续”而非直接 exit */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0; ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) {
//             sleep(1);
//             if (!g_run) break;
//             continue;
//         }
//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if (!lf) { sleep(1); continue; }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 /* 不再直接 exit，而是尝试重新定位 trusted prefix */
//                 log_msg("fseeko to %llu failed: attempt to recover trusted prefix", g_offset);
//                 trigger_alarm("Cannot seek to last known offset in audit.log; attempting recovery and continuing");
//                 /* try find trusted prefix using idx and log */
//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Recovery: set trusted state lines=%llu off=%llu and reopen", g_lines, g_offset);
//                     /* reopen lf and seek again */
//                     fclose(lf); lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { log_msg("reopen log failed after recovery"); return -1; }
//                     if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                         log_msg("seek failed even after recovery; set to file end and continue");
//                         fseeko(lf, 0, SEEK_END);
//                         g_offset = (unsigned long long)ftello(lf);
//                     }
//                 } else {
//                     log_msg("Recovery attempt failed: no trusted prefix found; seeking to file end and continue");
//                     fclose(lf);
//                     lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { sleep(1); continue; }
//                     fseeko(lf, 0, SEEK_END);
//                     g_offset = (unsigned long long)ftello(lf);
//                 }
//             }
//         }

//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line); line=NULL;
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run) break;
//         }

//         if (!g_run) break;

//         /* EOF：检查是否截断或 inode 改变，遇到则报警并尝试恢复（而非直接退出） */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) {
//             fclose(lf); lf=NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> attempt recovery and continue",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; attempting recovery and continue (manual review required)");
//             /* attempt to locate trusted prefix and set g_offset appropriately */
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after truncation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 log_msg("Recovery after truncation failed: no trusted prefix; setting g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain,0,sizeof(g_chain));
//                 g_lines = 0;
//             }
//             /* reposition lf for next loop */
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> attempt recovery and continue");
//             trigger_alarm("Audit log rotated; attempting recovery and continue (manual review required)");
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             if (find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off) == 0) {
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after rotation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 /* fallback: start from file end */
//                 log_msg("Recovery after rotation failed: no trusted prefix; set g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//             }
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         usleep(100000);
//     }

//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* normal exit */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     if (g_idx) fclose(g_idx);
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }
// 


//版本  处理（idx与seal   和  audit.log差一行内容导致后续审计报错）
/* gmseal-plugin.c  修订版：主要修复 idx 替换/重建后旧 FILE* 继续写入导致重复块的问题
   - 在重建/截断 idx 前关闭 g_idx
   - 重建/截断完成后重新 open idx（O_RDWR|O_CREAT|O_APPEND）
   - 所有 idx 写入使用 fd + dprintf（O_APPEND），并使用 flock(fd, LOCK_EX)
   - find_trusted_prefix 返回语义： 1 found (>0), 0 none(found 0), -1 error
   - 其他尽量保持原有逻辑
*/

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>
// #include <sys/time.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"     /* trigger_alarm */
// #include "../../lib/gm_crypto.h"   /* gm_sm2_sign, gm_sm2_verify, gm_crypto_init etc. */

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log)
//         return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// /* hex helpers */
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1)
//         return;
//     for (size_t i = 0; i < len; i++)
//     {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// /* base64 encode (no newlines) */
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) {
//         if (b64) BIO_free(b64);
//         if (mem) BIO_free(mem);
//         return NULL;
//     }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) {
//         BIO_free_all(b64);
//         return NULL;
//     }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// /* base64 decode (no newlines) */
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// /* file hash SM3 */
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// /* normalize */
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//         len--;
//     return len;
// }
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL;
//     size_t cap = 0;
//     ssize_t n;
//     unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1)
//         ln++;
//     free(line);
//     fclose(f);
//     return ln;
// }

// /* ------------ idx 管理与相关函数 ------------ */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL;
//     a->count = 0;
// }

// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out, 0, sizeof(*out));
//     FILE *f = fopen(path, "r");
//     if (!f) return -1;
//     char buf[1024];
//     IdxEntry *arr = NULL;
//     size_t cnt = 0, capv = 0;
//     while (fgets(buf, sizeof(buf), f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0;
//         char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii;
//             arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr;
//     out->count = cnt;
//     return 0;
// }

// /* find_trusted_prefix: 返回语义
//    1 -> 找到 trusted (last_good_i > 0)
//    0 -> 执行成功但没有 trusted lines (==0)
//   -1 -> error (无法读取)
// */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_offset) *out_offset = 0;
//         if (out_chain) memset(out_chain, 0, 32);
//         free_idx_array(&idx);
//         return -1;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32];
//     memset(H, 0, sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0;
//     unsigned long long last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         unsigned int mdlen = 0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     last_good_i = e->i;
//                     last_good_off = e->off + 0;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else {
//                     break;
//                 }
//             } else if (e->i < ln) {
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H, 32, hhex, sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else {
//                         break;
//                     }
//                 } else {
//                     break;
//                 }
//             } else {
//                 break;
//             }
//         } else {
//             break;
//         }
//     }

//     free(line);
//     fclose(lf);

//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);

//     free_idx_array(&idx);
//     if (last_good_i > 0) return 1;
//     return 0;
// }

// /* ---------- seal parse/verify helpers ---------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S, 0, sizeof(*S));
//     FILE *f = fopen(seal_path, "r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line, &cap, f)) != -1) {
//         if (line[0] == '#' || line[0] == 0) continue;
//         trimnl(line);
//         char *eq = strchr(line, '=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k=trimws(line);
//         char *v=trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
//     return buf;
// }

// /* 计算前 N 行链 */
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path, "rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//     {
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen=0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//         { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line);
//     fclose(fp);
//     memcpy(out32, H, 32);
//     return 0;
// }

// /* ---------- 全局状态 ---------- */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static int g_run = 1;
// static int g_degraded_mode = 0;

// /* signals */
// static void on_signal(int sig)
// {
//     if (sig == SIGINT || sig == SIGTERM)
//         g_run = 0;
// }

// /* idx handle management */
// static int g_idx_fd = -1;
// static FILE *g_idx = NULL;

// static void close_idx_handle(void) {
//     if (g_idx) { fclose(g_idx); g_idx = NULL; }
//     if (g_idx_fd >= 0) { close(g_idx_fd); g_idx_fd = -1; }
// }

// static int open_idx_handle(void) {
//     close_idx_handle();
//     int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
//     if (fd < 0) {
//         log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     FILE *f = fdopen(dup(fd), "a+");
//     if (!f) { log_msg("open_idx_handle: fdopen failed: %s", strerror(errno)); close(fd); return -1; }
//     g_idx_fd = fd;
//     g_idx = f;
//     fchmod(g_idx_fd, 0600);
//     return 0;
// }

// /* atomic append idx line (fd + O_APPEND + flock) */
// static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex) {
//     if (g_idx_fd < 0) {
//         log_msg("append_idx_line_atomic: idx fd not open");
//         return -1;
//     }
//     if (flock(g_idx_fd, LOCK_EX) != 0)
//         log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
//     int rv = 0;
//     if (dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n",
//                 (unsigned long long)i, (unsigned long long)off, lhex, chex) < 0) {
//         log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno));
//         rv = -1;
//     } else {
//         fsync(g_idx_fd);
//     }
//     flock(g_idx_fd, LOCK_UN);
//     return rv;
// }

// /* rebuild idx from log but atomic: write tmp then rename, and reopen idx handle */
// static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath, "rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned long long ln = 0, offset = 0;
//     unsigned char H[32] = {0};

//     /* close current idx handle to avoid writing to old inode */
//     close_idx_handle();

//     char tmppath[PATH_MAX];
//     snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line, &cap, lf)) != -1)
//     {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c1, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//         {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(c2, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(c2, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//         {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L, 32, lhex, sizeof(lhex));
//         hexdump(H, 32, chex, sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//         {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line);
//     fclose(lf);
//     fflush(tf);
//     fsync(fileno(tf));
//     fclose(tf);
//     if (rename(tmppath, idxpath) != 0) {
//         unlink(tmppath);
//         return -1;
//     }
//     chmod(idxpath, 0600);
//     /* reopen idx handle */
//     if (open_idx_handle() != 0) {
//         log_msg("rebuild_idx_from_log_atomic: open_idx_handle failed after rename");
//         return -1;
//     }
//     if (out_chain) memcpy(out_chain, H, 32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// /* truncate idx to keep_i using tmp+rename (atomic). reopen idx handle */
// static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
// {
//     /* close current idx handle */
//     close_idx_handle();

//     FILE *in = fopen(idxpath, "r");
//     if (!in) {
//         if (keep_i == 0) {
//             FILE *out = fopen(idxpath, "w");
//             if (!out) return -1;
//             fclose(out);
//             chmod(idxpath, 0600);
//             if (open_idx_handle() != 0) return -1;
//             return 0;
//         }
//         return -1;
//     }
//     char tmp[PATH_MAX];
//     snprintf(tmp, sizeof(tmp), "%s.trunc", idxpath);
//     FILE *out = fopen(tmp, "w");
//     if (!out) { fclose(in); return -1; }
//     char buf[2048];
//     while (fgets(buf, sizeof(buf), in)) {
//         if (buf[0] == '#') {
//             if (fputs(buf, out) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; }
//             continue;
//         }
//         unsigned long long i=0, off=0;
//         char lhex[256] = {0}, chex[256] = {0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%255s chash=%255s", &i, &off, lhex, chex) == 4) {
//             if (i <= keep_i) {
//                 if (fprintf(out, "i=%llu off=%llu lhash=%s chash=%s\n", i, off, lhex, chex) < 0) {
//                     fclose(in); fclose(out); unlink(tmp); return -1;
//                 }
//             } else {
//                 break;
//             }
//         } else {
//             /* skip */
//         }
//     }
//     fclose(in);
//     fflush(out);
//     fsync(fileno(out));
//     fclose(out);
//     if (rename(tmp, idxpath) != 0) { unlink(tmp); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("truncate_idx_to_i_atomic: open_idx_handle failed after rename"); return -1; }
//     return 0;
// }

// /* ---------- 验证/恢复现有 seal ---------- */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* no seal present, allow init from scratch */
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal not SM2/SM3 or SM2_ID mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }
//     /* verify signature */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der;
//     X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint check */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if(ctx)EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }
//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1;
//     }
//     size_t siglen=0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     /* chain check: recompute chain on log first N lines */
//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             /* 链不匹配 -> 进入降级处理：尝试定位 trusted prefix 并继续运行 */
//             log_msg("Existing seal chain mismatch with current audit.log; attempting to compute trusted prefix and continue in degraded mode");
//             trigger_alarm("Existing seal chain mismatch with audit.log; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret == -1) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix found; set trusted_lines=0 and continue (manual intervention required)");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 g_degraded_mode = 1;
//             } else if (ret == 0) {
//                 log_msg("find_trusted_prefix found no trusted prefix; entering degraded mode");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 g_degraded_mode = 1;
//             } else {
//                 /* truncate idx to trusted_lines atomically */
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) {
//                     log_msg("truncate idx to trusted_lines=%llu failed", trusted_lines);
//                     memset(g_chain, 0, sizeof(g_chain));
//                     g_lines = 0;
//                     g_offset = 0;
//                     g_degraded_mode = 1;
//                 } else {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     g_degraded_mode = 0;
//                     log_msg("Trusted prefix located: lines=%llu offset=%llu", g_lines, g_offset);
//                 }
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//     } else {
//         /* records=0, ok */
//     }

//     /* idx check */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3 for existing seal");
//             return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             /* idx 被修改，报警并尝试定位可信前缀，同上 */
//             log_msg("Existing seal index_sm3 mismatch - idx file changed; attempting to find trusted prefix and continue");
//             trigger_alarm("Index file hash mismatch; degraded mode (continue) - manual review required");

//             unsigned long long trusted_lines = 0;
//             unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret == -1 || ret == 0) {
//                 log_msg("find_trusted_prefix failed - no trusted prefix; set to degraded mode");
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 g_offset = 0;
//                 g_degraded_mode = 1;
//             } else {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) {
//                     log_msg("truncate idx to trusted_lines=%llu failed", trusted_lines);
//                     memset(g_chain, 0, sizeof(g_chain));
//                     g_lines = 0;
//                     g_offset = 0;
//                     g_degraded_mode = 1;
//                 } else {
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     g_degraded_mode = 0;
//                     log_msg("Trusted prefix located after idx mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//                 }
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//         /* tail check */
//         FILE *ix = fopen(IDX_PATH, "r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65]={0};
//             while (fgets(buf, sizeof(buf), ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("Existing seal vs idx tail mismatch; try to find trusted prefix and continue");
//                 trigger_alarm("Seal vs idx tail mismatch; degraded mode (continue) - manual review required");

//                 unsigned long long trusted_lines = 0;
//                 unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//                 if (ret == -1 || ret == 0) {
//                     log_msg("find_trusted_prefix failed - no trusted prefix; set to degraded mode");
//                     memset(g_chain,0,sizeof(g_chain));
//                     g_lines = 0;
//                     g_offset = 0;
//                     g_degraded_mode = 1;
//                 } else {
//                     if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) {
//                         log_msg("truncate idx to trusted_lines=%llu failed", trusted_lines);
//                         memset(g_chain,0,sizeof(g_chain));
//                         g_lines = 0;
//                         g_offset = 0;
//                         g_degraded_mode = 1;
//                     } else {
//                         memcpy(g_chain, trusted_chain, 32);
//                         g_lines = trusted_lines;
//                         g_offset = trusted_off;
//                         g_degraded_mode = 0;
//                         log_msg("Trusted prefix located after idx tail mismatch: lines=%llu offset=%llu", g_lines, g_offset);
//                     }
//                 }
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* parse chain into g_chain */
//     memset(g_chain,0,32);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi,&lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i]= (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records,NULL,10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Cannot open idx for reading offset");
//         return -1;
//     }
//     char buf[1024]; unsigned long long last_off=0; unsigned long long last_i=0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i=i; last_off=off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         return -1;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* main line hashing and idx append */
// static int gmseal_on_line(char *line_buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = normalize_line(line_buf, raw_n);
//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32)
//     {
//         EVP_MD_CTX_free(ctx);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, line_buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32)
//     {
//         EVP_MD_CTX_free(ctx2);
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (g_idx_fd < 0) {
//         if (open_idx_handle() != 0) {
//             log_msg("gmseal_on_line: open_idx_handle failed");
//             return -1;
//         }
//     }

//     if (append_idx_line_atomic(g_lines, g_offset, lhex, chex) != 0) {
//         log_msg("failed write idx line for i=%llu", (unsigned long long)g_lines);
//         return -1;
//     }

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* write seal (atomic) */
// static int gmseal_write_seal(void)
// {
//     if (g_degraded_mode) {
//         log_msg("gmseal_write_seal: in degraded mode, skip writing seal");
//         return 0;
//     }

//     EVP_PKEY *pkey = NULL;
//     X509 *cert = NULL;

//     /* read pass */
//     FILE *pf = fopen(PASS_PATH, "r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n=getline(&secret,&cap,pf); fclose(pf);
//     if (n<=0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n] = 0;

//     /* read salt */
//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH, "r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf, sizeof(saltbuf), sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen=strlen(saltbuf);
//     while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen] = 0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++) {
//         unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x",&hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//         salt[i] = (hi<<4)|lo;
//     }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS,
//                            EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//     {
//         log_msg("PBKDF2-SM3 failed");
//         OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     /* load p12 */
//     FILE *pf2 = fopen(P12_PATH, "rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//     fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) {
//         log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1;
//     }
//     PKCS12_free(p12);

//     /* fingerprint */
//     unsigned char *der=NULL; int derlen=i2d_X509(cert,&der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx ||
//         EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//         EVP_DigestUpdate(ctx, der, derlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//     {
//         if (ctx) EVP_MD_CTX_free(ctx);
//         OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx);
//     OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) {
//         EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1;
//     }

//     /* canonical */
//     char *canon=NULL;
//     if (asprintf(&canon,
//         "version=1\n"
//         "hash=sm3\n"
//         "sign=sm2\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%llu\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//     {
//         EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }

//     /* sign */
//     unsigned char *sig=NULL; size_t siglen=0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed");
//         free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     /* certb64 */
//     unsigned char *der2=NULL; int der2len=i2d_X509(cert,&der2);
//     char *certb64=NULL;
//     if (der2len > 0) {
//         certb64 = b64_encode(der2, der2len);
//         OPENSSL_free(der2);
//     } else {
//         log_msg("i2d_X509 failed");
//         free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* atomic write seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) {
//         log_msg("open seal tmp failed: %s", strerror(errno));
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) {
//         log_msg("rename seal tmp failed: %s", strerror(errno));
//         unlink(tmp);
//         free(canon); free(sigb64); free(certb64); return -1;
//     }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 初始化：不再自动重建 idx；如有 existing seal 必须严格匹配或进入降级模式（trusted prefix） */
// static int gmseal_init(void)
// {
//     memset(g_chain, 0, sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     /* 打开/创建 idx 文件 */
//     if (open_idx_handle() != 0) {
//         log_msg("open %s failed (will attempt later)", IDX_PATH);
//         /* 继续：verify_existing_seal 可能会 rebuild idx (atomic函数会 open) */
//     }

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch with audit.log / idx. Aborting.");
//         return -1;
//     }
//     if (vs > 0) {
//         /* 没有 seal，首次启动：从 log 重构 idx（允许） */
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log_atomic failed (first run)");
//             return -1;
//         }
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines;
//         g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         log_msg("existing seal verified or degraded-mode prepared. state lines=%llu off=%llu", g_lines, g_offset);
//         if (g_idx_fd < 0) {
//             if (open_idx_handle() != 0)
//                 log_msg("gmseal_init: open_idx_handle failed after verify");
//         }
//     }
//     return 0;
// }

// /* Tail: 当检测到截断/inode 改变等问题时，进入“尝试恢复 trusted prefix 并继续”而非直接退出 */
// static int tail_and_process(void)
// {
//     FILE *lf = NULL;
//     char *line = NULL;
//     size_t cap = 0; ssize_t n;

//     for (;;)
//     {
//         struct stat st;
//         if (stat(LOG_PATH, &st) != 0) {
//             sleep(1);
//             if (!g_run) break;
//             continue;
//         }
//         if (!lf) {
//             lf = fopen(LOG_PATH,"rb");
//             if (!lf) { sleep(1); continue; }
//             /* seek 到 g_offset */
//             if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                 /* 不再直接 exit，而是尝试重新定位 trusted prefix */
//                 log_msg("fseeko to %llu failed: attempt to recover trusted prefix", g_offset);
//                 trigger_alarm("Cannot seek to last known offset in audit.log; attempting recovery and continuing");
//                 /* try find trusted prefix using idx and log */
//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//                 if (ret == 1) {
//                     if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0)
//                         log_msg("truncate idx to trusted_lines=%llu failed during recovery", trusted_lines);
//                     memcpy(g_chain, trusted_chain, 32);
//                     g_lines = trusted_lines;
//                     g_offset = trusted_off;
//                     log_msg("Recovery: set trusted state lines=%llu off=%llu and reopen", g_lines, g_offset);
//                     /* reopen lf and seek again */
//                     fclose(lf); lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { log_msg("reopen log failed after recovery"); return -1; }
//                     if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//                         log_msg("seek failed even after recovery; set to file end and continue");
//                         fseeko(lf, 0, SEEK_END);
//                         g_offset = (unsigned long long)ftello(lf);
//                     }
//                 } else if (ret == 0) {
//                     log_msg("Recovery attempt failed: no trusted prefix found; seeking to file end and continue");
//                     fclose(lf);
//                     lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { sleep(1); continue; }
//                     fseeko(lf, 0, SEEK_END);
//                     g_offset = (unsigned long long)ftello(lf);
//                 } else {
//                     log_msg("Recovery attempt error: seeking to file end and continue");
//                     fclose(lf);
//                     lf = fopen(LOG_PATH,"rb");
//                     if (!lf) { sleep(1); continue; }
//                     fseeko(lf, 0, SEEK_END);
//                     g_offset = (unsigned long long)ftello(lf);
//                 }
//             }
//         }

//         while ((n = getline(&line, &cap, lf)) != -1) {
//             if (n > 0) {
//                 if (gmseal_on_line(line, (size_t)n) != 0) {
//                     log_msg("gmseal_on_line failed at next line (records=%llu)", (unsigned long long)(g_lines + 1));
//                     trigger_alarm("Hashing error when processing new audit line; exit");
//                     free(line); line=NULL;
//                     fclose(lf); lf=NULL;
//                     return -1;
//                 }
//                 if (ALWAYS_REFRESH_SEAL) {
//                     if (gmseal_write_seal() != 0)
//                         log_msg("gmseal_write_seal failed");
//                 }
//             }
//             if (!g_run) break;
//         }

//         if (!g_run) break;

//         /* EOF：检查是否截断或 inode 改变，遇到则报警并尝试恢复（而非直接退出） */
//         clearerr(lf);
//         struct stat st2;
//         if (stat(LOG_PATH, &st2) != 0) {
//             fclose(lf); lf=NULL;
//             sleep(1);
//             continue;
//         }
//         if ((unsigned long long)st2.st_size < g_offset) {
//             log_msg("audit.log truncated: file size %llu < last offset %llu -> attempt recovery and continue",
//                     (unsigned long long)st2.st_size, g_offset);
//             trigger_alarm("Audit log truncated; attempting recovery and continue (manual review required)");
//             /* attempt to locate trusted prefix and set g_offset appropriately */
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret == 1) {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) log_msg("truncate idx to trusted_lines failed after truncation detection");
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after truncation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 log_msg("Recovery after truncation failed: no trusted prefix; setting g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain,0,sizeof(g_chain));
//                 g_lines = 0;
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) log_msg("truncate idx to 0 failed after truncation detection");
//             }
//             /* reposition lf for next loop */
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         if (st2.st_ino != st.st_ino) {
//             log_msg("audit.log inode changed (rotation) -> attempt recovery and continue");
//             trigger_alarm("Audit log rotated; attempting recovery and continue (manual review required)");
//             unsigned long long trusted_lines=0; unsigned char trusted_chain[32]; unsigned long long trusted_off=0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret == 1) {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) log_msg("truncate idx to trusted_lines failed after rotation");
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("Recovery after rotation: trusted_lines=%llu off=%llu", g_lines, g_offset);
//             } else {
//                 /* fallback: start from file end */
//                 log_msg("Recovery after rotation failed: no trusted prefix; set g_offset to file end");
//                 g_offset = (unsigned long long)st2.st_size;
//                 memset(g_chain, 0, sizeof(g_chain));
//                 g_lines = 0;
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) log_msg("truncate idx to 0 failed after rotation");
//             }
//             fclose(lf); lf = NULL;
//             continue;
//         }
//         usleep(100000);
//     }

//     if (line) free(line);
//     if (lf) fclose(lf);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0)
//     {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0)
//     {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main tail loop */
//     if (tail_and_process() != 0) {
//         log_msg("tail_and_process aborted due to detected issues");
//     }

//     /* normal exit */
//     if (gmseal_write_seal() != 0)
//         log_msg("final seal write failed");
//     close_idx_handle();
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }



//版本  stdin.c

/* 完整 gmseal-plugin.c
 * 设计说明：
 *  - 从 audispd 接受事件（stdin 行输入），每当收到一条事件，
 *    从 audit.log 当前已知偏移 g_offset 读取下一行完整字节，
 *    计算该行 lhash, 更新链 chash, 将 idx 原子 append（i, off, lhash, chash）
 *  - 在需要重建或截断 idx 时先关闭 idx fd，再写 tmp 再 rename，再重新 open idx fd
 *  - 在检测 seal/idx 与 audit.log 不一致时尝试 find_trusted_prefix()
 *  - 只有在首次启动且没有 seal 时允许 rebuild idx；在降级模式下不会自动覆盖 seal
 *
 * 请务必与项目其他依赖（gm_crypto, alarm.c）一起编译。
 */

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>
// #include <sys/time.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"     
// #include "../../lib/gm_crypto.h"   

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"   //记录每一行审计日志的偏移量、行哈希（lhash）和链式哈希（chash）
// #define SEAL_PATH "/var/log/audit/audit.log.seal" //用 SM2 算法对索引文件(idx)哈希、日志链式哈希等关键信息签名，形成 “数字封印”

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可修改为周期写） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// static FILE *g_log = NULL;

// //创建日志目录（/var/log/audit-gm）并打开工具日志文件（audisp-gmseal.log）
// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }

// //格式化写入日志（包含时间戳、日志内容），支持可变参数
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log) return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }

// //将二进制数据（如 SM3 哈希结果）转为十六进制字符串
// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// //将二进制数据（如 X509 证书、SM2 签名）转为 Base64 字符串（无换行）
// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) { if (b64) BIO_free(b64); if (mem) BIO_free(mem); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) { BIO_free_all(b64); return NULL; }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// //将 Base64 字符串（如密封文件中的cert_der_base64）转回二进制数据
// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// //计算整个文件的 SM3 哈希，并返回十六进制字符串
// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// //去除日志行尾的\n或\r（统一换行格式）
// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) len--;
//     return len;
// }

// //统计文件总行数
// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL; size_t cap = 0; ssize_t n; unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line); fclose(f); return ln;
// }

// /* ------------ idx helpers & atomic operations ------------ */

// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL; a->count = 0;
// }

// //加载索引文件idx索引文件记录每一行审计日志的偏移量、行哈希（lhash）和链式哈希（chash）。
// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out,0,sizeof(*out));
//     FILE *f = fopen(path,"r");
//     if (!f) return -1;
//     char buf[1024];
//     IdxEntry *arr = NULL; size_t cnt = 0, capv = 0;
//     while (fgets(buf,sizeof(buf),f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&ii,&off,lhex,chex)==4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii; arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr; out->count = cnt;
//     return 0;
// }


// //在日志文件和索引文件不一致时，查找可信的前缀（即索引文件和日志文件能够匹配的最大行数）
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_chain) memset(out_chain,0,32);
//         if (out_offset) *out_offset = 0;
//         free_idx_array(&idx);
//         return 0; /* no idx -> no trusted prefix */
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32]; memset(H,0,sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0, last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned int mdlen=0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     last_good_i = e->i;
//                     last_good_off = e->off;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else break;
//             } else if (e->i < ln) {
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else break;
//                 } else break;
//             } else break;
//         } else break;
//     }

//     free(line); fclose(lf);
//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);
//     free_idx_array(&idx);
//     if (last_good_i > 0) return 1;
//     return 0;
// }

// //存储密封文件的所有字段
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// //去除字符串尾的\n或\r
// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// //去除字符串首尾的空白字符（空格、制表符、换行符）
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}

// //读取密封文件，解析键值对到struct seal，并检查所有必要字段是否存在
// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S,0,sizeof(*S));
//     FILE *f = fopen(seal_path,"r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line,&cap,f)) != -1) {
//         if (line[0]=='#' || line[0]==0) continue;
//         trimnl(line);
//         char *eq = strchr(line,'=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// //从struct seal构建 “规范数据字符串”（固定字段顺序和格式)
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version,S->hash,S->sign,S->sm2_id,S->path,S->records,S->chain,S->key_fpr,S->index_sm3) < 0) return NULL;
//     return buf;
// }

// //计算审计日志前limit_lines行的链式 SM3 哈希(用于验证密封文件中的chain字段)
// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path,"rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line,&cap,fp)) != -1) {
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen = 0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line); fclose(fp); memcpy(out32,H,32); return 0;
// }

// /* ---------- idx handle 管理与原子操作 ---------- */
// static int g_idx_fd = -1;
// static FILE *g_idx = NULL;

// //关闭索引文件的文件描述符和FILE*指针。
// static void close_idx_handle(void) {
//     if (g_idx) { fclose(g_idx); g_idx = NULL; }
//     if (g_idx_fd >= 0) { close(g_idx_fd); g_idx_fd = -1; }
// }

// //打开索引文件（用于原子操作），并设置文件描述符和FILE*指针。
// static int open_idx_handle(void) {
//     close_idx_handle();
//     int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
//     if (fd < 0) {
//         log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     /* duplicate fd for stdio to allow FILE* operations in other places (if needed) */
//     FILE *f = fdopen(dup(fd), "a+");
//     if (!f) { log_msg("open_idx_handle: fdopen failed: %s", strerror(errno)); close(fd); return -1; }
//     g_idx_fd = fd;
//     g_idx = f;
//     fchmod(g_idx_fd, 0600);
//     return 0;
// }



// //原子性地向索引文件追加一行记录（使用文件锁确保多进程安全）
// static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex) {
//     if (g_idx_fd < 0) {
//         log_msg("append_idx_line_atomic: idx fd not open");
//         return -1;
//     }
//     if (flock(g_idx_fd, LOCK_EX) != 0) {
//         log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
//     }
//     int ret = 0;
//     ssize_t w = dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)i, (unsigned long long)off, lhex, chex);
//     if (w < 0) { log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno)); ret = -1; }
//     else fsync(g_idx_fd);
//     flock(g_idx_fd, LOCK_UN);
//     return ret;
// }

// //原子性地从日志文件重建索引文件（先写入临时文件，然后重命名）。用于初始化或恢复索引文件。
// static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath, unsigned char out_chain[32], unsigned long long *out_lines, unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     unsigned long long ln=0, offset=0;
//     unsigned char H[32]; memset(H,0,sizeof(H));

//     /* close idx handle */
//     close_idx_handle();

//     char tmppath[PATH_MAX];
//     snprintf(tmppath,sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line,&cap,lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new(); unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL)!=1 || EVP_DigestUpdate(c1, line, normlen)!=1 || EVP_DigestFinal_ex(c1,L,&lm)!=1 || lm!=32) {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new(); unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL)!=1 || EVP_DigestUpdate(c2, H, sizeof(H))!=1 || EVP_DigestUpdate(c2, line, normlen)!=1 || EVP_DigestFinal_ex(c2, newH, &hm)!=1 || hm!=32) {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L,32,lhex,sizeof(lhex));
//         hexdump(H,32,chex,sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n", (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line); fclose(lf);
//     fflush(tf); fsync(fileno(tf)); fclose(tf);
//     if (rename(tmppath, idxpath) != 0) { unlink(tmppath); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("rebuild: open_idx_handle failed after rename"); return -1; }
//     if (out_chain) memcpy(out_chain,H,32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// //原子性地截断索引文件，只保留前keep_i行记录。
// static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
// {
//     close_idx_handle();
//     FILE *in = fopen(idxpath,"r");
//     if (!in) {
//         if (keep_i == 0) {
//             FILE *out = fopen(idxpath,"w");
//             if (!out) return -1;
//             fclose(out);
//             chmod(idxpath, 0600);
//             if (open_idx_handle() != 0) return -1;
//             return 0;
//         }
//         return -1;
//     }
//     char tmp[PATH_MAX];
//     snprintf(tmp,sizeof(tmp), "%s.trunc", idxpath);
//     FILE *out = fopen(tmp, "w");
//     if (!out) { fclose(in); return -1; }
//     char buf[2048];
//     while (fgets(buf,sizeof(buf),in)) {
//         if (buf[0]=='#') { if (fputs(buf,out) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; } continue; }
//         unsigned long long i=0, off=0; char lhex[256]={0}, chex[256]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%255s chash=%255s",&i,&off,lhex,chex)==4) {
//             if (i <= keep_i) {
//                 if (fprintf(out,"i=%llu off=%llu lhash=%s chash=%s\n",i,off,lhex,chex) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; }
//             } else break;
//         }
//     }
//     fclose(in);
//     fflush(out); fsync(fileno(out)); fclose(out);
//     if (rename(tmp, idxpath) != 0) { unlink(tmp); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("truncate: open_idx_handle failed after rename"); return -1; }
//     return 0;
// }

// /* ---------- global state ---------- */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static int g_run = 1;
// static int g_degraded_mode = 0;

// //信号处理函数，用于处理SIGINT和SIGTERM信号，设置退出标志
// static void on_signal(int sig) { if (sig == SIGINT || sig == SIGTERM) g_run = 0; }

// //验证现有的密封文件（包括签名、证书、链式哈希等）。如果验证失败，则尝试进入降级模式，并截断索引文件到可信前缀。
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1;
//     }
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal algorithm mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }
//     /* verify signature & cert */
//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der; X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* fingerprint */
//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1; }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv,mdlen,fpr_hex,sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }

//     /* verify signature */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1; }
//     size_t siglen = 0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal signature invalid"); return -1; }

//     unsigned long long N = strtoull(S.records, NULL, 10);
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR,32,chx,sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             log_msg("Existing seal chain mismatch; attempting trusted prefix and degraded mode");
//             trigger_alarm("Existing seal chain mismatch - degraded mode (manual review required)");

//             unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 1) {
//                 log_msg("find_trusted_prefix found none; entering degraded mode and trunc idx to 0");
//                 g_degraded_mode = 1;
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) log_msg("truncate idx to 0 failed");
//                 memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//             } else {
//                 log_msg("trusted prefix located lines=%llu off=%llu; trunc idx accordingly", trusted_lines, trusted_off);
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) log_msg("truncate idx to trusted_lines failed");
//                 memcpy(g_chain, trusted_chain, 32); g_lines = trusted_lines; g_offset = trusted_off;
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//     }

//     /* idx check: compute idx sm3 and compare with S.index_sm3 */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Cannot compute idx sm3"); return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             log_msg("Index file hash mismatch; attempting trusted prefix and degraded mode");
//             trigger_alarm("Index file hash mismatch - degraded mode (manual review required)");
//             unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//             if (ret != 1) {
//                 log_msg("find_trusted_prefix none; degraded mode and truncate idx to 0");
//                 g_degraded_mode = 1;
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) log_msg("truncate idx to 0 failed");
//                 memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//             } else {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) log_msg("truncate idx to trusted_lines failed");
//                 memcpy(g_chain, trusted_chain, 32); g_lines = trusted_lines; g_offset = trusted_off;
//                 log_msg("trusted prefix located after idx mismatch: lines=%llu off=%llu", g_lines, g_offset);
//             }
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//         /* tail check: idx last entry must match seal.chain */
//         FILE *ix = fopen(IDX_PATH,"r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65] = {0};
//             while (fgets(buf,sizeof(buf),ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("seal vs idx tail mismatch; attempt trusted prefix and degraded mode");
//                 trigger_alarm("Seal vs idx tail mismatch - degraded mode (manual review required)");
//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//                 if (ret != 1) {
//                     log_msg("find_trusted_prefix none; degraded mode");
//                     g_degraded_mode = 1;
//                     if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) log_msg("truncate idx to 0 failed");
//                     memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//                 } else {
//                     if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) log_msg("truncate idx to trusted_lines failed");
//                     memcpy(g_chain, trusted_chain, 32); g_lines = trusted_lines; g_offset = trusted_off;
//                     log_msg("trusted prefix located after idx tail mismatch: lines=%llu off=%llu", g_lines, g_offset);
//                 }
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* If we reach here, everything matched; parse chain into g_chain */
//     memset(g_chain,0,sizeof(g_chain));
//    //-----在g_chain后，加上一部确认
//    unsigned char chk_chain[32];
//    if (sm3_chain_file_limit_local(LOG_PATH, g_lines, chk_chain) == 0) {
//       memcpy(g_chain, chk_chain, 32);
//    }

//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi, &lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i] = (hi<<4)|lo;
//     }
//     g_lines = strtoull(S.records, NULL, 10);

//     /* compute offset from idx last off */
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Cannot open idx for reading offset"); return -1; }
//     char buf[1024]; unsigned long long last_off = 0; unsigned long long last_i = 0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i = i; last_off = off;
//         }
//     }
//     fclose(ix);
//     if (last_i != g_lines) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing idx last_i != seal records - inconsistent"); return -1; }
//     g_offset = last_off;
//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// //处理从日志缓冲区读取的一行日志，计算该行的SM3哈希和链式哈希，并将结果原子性地追加到索引文件。
// static int gmseal_on_line_from_logbuf(const char *buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = raw_n;
//     while (normlen > 0 && (buf[normlen-1]=='\n' || buf[normlen-1]=='\r')) normlen--;

//     unsigned char lhash[32], newH[32];
//     unsigned int lm=0, hm=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) return -1;
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx, lhash, &lm) != 1 || lm != 32) {
//         EVP_MD_CTX_free(ctx); return -1;
//     }
//     EVP_MD_CTX_free(ctx);

//     EVP_MD_CTX *ctx2 = EVP_MD_CTX_new();
//     if (!ctx2) return -1;
//     if (EVP_DigestInit_ex(ctx2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(ctx2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(ctx2, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(ctx2, newH, &hm) != 1 || hm != 32) {
//         EVP_MD_CTX_free(ctx2); return -1;
//     }
//     EVP_MD_CTX_free(ctx2);

//     memcpy(g_chain, newH, 32);
//     g_lines++;

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(g_chain, 32, chex, sizeof(chex));

//     if (g_idx_fd < 0) {
//         if (open_idx_handle() != 0) {
//             log_msg("gmseal_on_line: open_idx_handle failed");
//             return -1;
//         }
//     }

//     if (append_idx_line_atomic(g_lines, g_offset, lhex, chex) != 0) {
//         log_msg("gmseal_on_line: append_idx_line_atomic failed for i=%llu", (unsigned long long)g_lines);
//         return -1;
//     }

//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// //使用SM2私钥对当前状态（包括链式哈希、索引文件哈希等）进行签名，并写入密封文件。
// static int gmseal_write_seal(void)
// {
//     if (g_degraded_mode) {
//         log_msg("In degraded mode: skip writing seal");
//         return 0;
//     }

//     EVP_PKEY *pkey = NULL; X509 *cert = NULL;
//     FILE *pf = fopen(PASS_PATH,"r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n = getline(&secret, &cap, pf); fclose(pf);
//     if (n <= 0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n]=0;

//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH,"r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf,sizeof(saltbuf),sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen = strlen(saltbuf); while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++){ unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x", &hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; } salt[i] = (hi<<4)|lo; }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived)) {
//         log_msg("PBKDF2-SM3 failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     FILE *pf2 = fopen(P12_PATH,"rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) { log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1; }
//     PKCS12_free(p12);

//     unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) { EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1; }

//     char *canon = NULL;
//     if (asprintf(&canon, "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     unsigned char *sig=NULL; size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     char *certb64 = NULL;
//     if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); } else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     EVP_PKEY_free(pkey); X509_free(cert);

//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) { log_msg("rename seal tmp failed: %s", strerror(errno)); unlink(tmp); free(canon); free(sigb64); free(certb64); return -1; }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// //初始化插件状态，包括验证现有密封文件、重建索引文件等
// // static int gmseal_init(void)
// // {
// //     memset(g_chain,0,sizeof(g_chain));
// //     g_lines = 0; g_offset = 0; g_degraded_mode = 0;

// //     /* open idx handle early (may be replaced during verify) */
// //     if (open_idx_handle() != 0) {
// //         log_msg("warning: cannot open idx on init; will attempt later");
// //     }

// //     int vs = verify_existing_seal();
// //     if (vs < 0) {
// //         log_msg("Existing seal mismatch/verify error - aborting");
// //         return -1;
// //     }
// //     if (vs > 0) {
// //         /* no seal -> first run: rebuild idx from log and set state */
// //         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
// //         if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
// //             log_msg("rebuild_idx_from_log_atomic failed (first run)"); return -1;
// //         }
// //         memcpy(g_chain, new_chain, 32); g_lines = new_lines; g_offset = new_off;
// //         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
// //     } else {
// //         log_msg("existing seal verified or degraded prepared. state lines=%llu off=%llu", g_lines, g_offset);
// //     }
// //     return 0;
// // }

// static int gmseal_init(void)
// {
//     memset(g_chain,0,sizeof(g_chain));
//     g_lines = 0; g_offset = 0; g_degraded_mode = 0;

//     if (open_idx_handle() != 0) {
//         log_msg("warning: cannot open idx on init; will attempt later");
//     }

//     int vs = verify_existing_seal();
//     if (vs < 0) {
//         log_msg("Existing seal mismatch/verify error - aborting");
//         return -1;
//     }

//     unsigned long long log_lines = count_file_lines_simple(LOG_PATH);
//     if (vs > 0) {
//         // no seal -> first run
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log_atomic failed (first run)");
//             return -1;
//         }
//         memcpy(g_chain, new_chain, 32);
//         g_lines = new_lines; g_offset = new_off;
//         log_msg("first-run rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//     } else {
//         // existing seal verified
//         if (log_lines > g_lines) {
//             log_msg("log has %llu lines, seal has %llu -> resync required", log_lines, g_lines);
//             unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//             if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//                 log_msg("rebuild_idx_from_log_atomic failed (resync)");
//                 return -1;
//             }
//             memcpy(g_chain, new_chain, 32);
//             g_lines = new_lines; g_offset = new_off;
//             log_msg("resync rebuild idx done: lines=%llu off=%llu", g_lines, g_offset);
//         } else if (log_lines < g_lines) {
//             log_msg("log truncated: log_lines=%llu < seal.records=%llu", log_lines, g_lines);
//             g_degraded_mode = 1;
//             if (truncate_idx_to_i_atomic(IDX_PATH, log_lines) != 0)
//                 log_msg("truncate idx to %llu failed", log_lines);
//             g_lines = log_lines; g_offset = 0;
//             memset(g_chain,0,sizeof(g_chain));
//         } else {
//             log_msg("existing seal verified and in sync: lines=%llu off=%llu", g_lines, g_offset);
//         }
//     }
//     return 0;
// }


// //从审计日志的当前偏移读取一行，并调用gmseal_on_line_from_logbuf处理该行。
// static int process_one_audisp_line(void)
// {
//     /* Read one full line from audit.log at offset g_offset */
//     FILE *lf = fopen(LOG_PATH, "rb");
//     if (!lf) {
//         log_msg("process_one_audisp_line: open audit.log failed: %s", strerror(errno));
//         return -1;
//     }
//     /* seek to g_offset */
//     if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//         log_msg("process_one_audisp_line: fseeko to %llu failed: %s", g_offset, strerror(errno));
//         fclose(lf);
//         return -1;
//     }
//     /* Read next line bytes (we'll use getline on this FILE*) */
//     char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, lf);
//     if (n <= 0) {
//         /* nothing readable yet: maybe auditd hasn't flushed; close and return, caller may retry */
//         free(line); fclose(lf);
//         return 1; /* indicate retry needed */
//     }
//     /* compute and append */
//     int rc = gmseal_on_line_from_logbuf(line, (size_t)n);
//     if (rc != 0) {
//         log_msg("process_one_audisp_line: gmseal_on_line_from_logbuf failed");
//     }
//     free(line); fclose(lf);
//     return rc;
// }

// //主事件循环，从标准输入（audispd发送的事件）读取每一行，并触发对审计日志相应行的处理。
// static int stdin_event_loop(void)
// {
//     char *line = NULL; size_t cap = 0;
//     ssize_t n;

//     /* read from stdin (audispd will write formatted event lines) */
//     while (g_run && (n = getline(&line, &cap, stdin)) != -1) {
//         /* a plugin input line arrived: for each such arrival, read corresponding audit.log line bytes */
//         /* We might receive empty lines occasionally; ignore */
//         if (n <= 0) continue;

//         /* try read next audit.log line; if not available yet, attempt a few times (small sleep) */
//         int attempt = 0, rc;
//         do {
//             rc = process_one_audisp_line();
//             if (rc == 0) break;
//             if (rc == 1) { /* retry: not yet ready */
//                 usleep(100000); /* 100ms */
//                 attempt++;
//             } else break;
//         } while (attempt < 30 && g_run);

//         if (rc != 0 && rc != 1) {
//             /* real error */
//             log_msg("stdin_event_loop: processing incoming audisp line failed");
//             trigger_alarm("gmseal plugin processing error - exiting");
//             free(line);
//             return -1;
//         }

//         /* after successfully processing a new line, optionally refresh seal */
//         if (ALWAYS_REFRESH_SEAL) {
//             if (gmseal_write_seal() != 0) log_msg("gmseal_write_seal failed");
//         }
//     }

//     if (line) free(line);
//     return 0;
// }

// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (gm_crypto_init() != 0) {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0) {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         if (g_log) fclose(g_log);
//         return 2;
//     }

//     /* initial seal write (if allowed) */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* main loop: read audispd events from stdin and process */
//     if (stdin_event_loop() != 0) {
//         log_msg("stdin_event_loop aborted due to errors");
//     }

//     /* final seal */
//     if (gmseal_write_seal() != 0) log_msg("final seal write failed");

//     close_idx_handle();
//     gm_crypto_cleanup();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }


//版本  1、行不一致问题解决
// gmseal-plugin.c (singleton lock + hard reconcile before seal + dup-append guard)
// 2025-09-08 final

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>
// #include <sys/time.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可改成周期） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// /* singleton lock */
// #define RUN_LOCK "/var/run/audisp-gmseal.lock"

// static FILE *g_log = NULL;
// static int g_run_lock_fd = -1;

// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log) return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }
// static int acquire_singleton_lock(void)
// {
//     g_run_lock_fd = open(RUN_LOCK, O_CREAT|O_RDWR, 0644);
//     if (g_run_lock_fd < 0) {
//         log_msg("open run lock %s failed: %s", RUN_LOCK, strerror(errno));
//         return -1;
//     }
//     if (flock(g_run_lock_fd, LOCK_EX | LOCK_NB) != 0) {
//         log_msg("another gmseal instance is running (lock busy), exiting");
//         return -1;
//     }
//     dprintf(g_run_lock_fd, "%ld\n", (long)getpid());
//     return 0;
// }
// static void release_singleton_lock(void)
// {
//     if (g_run_lock_fd >= 0) {
//         flock(g_run_lock_fd, LOCK_UN);
//         close(g_run_lock_fd);
//         unlink(RUN_LOCK); /* 容忍失败 */
//         g_run_lock_fd = -1;
//     }
// }

// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) { if (b64) BIO_free(b64); if (mem) BIO_free(mem); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) { BIO_free_all(b64); return NULL; }
//     BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) len--;
//     return len;
// }

// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL; size_t cap = 0; ssize_t n; unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line); fclose(f); return ln;
// }

// /* ------------ idx helpers ------------ */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL; a->count = 0;
// }

// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out,0,sizeof(*out));
//     FILE *f = fopen(path,"r");
//     if (!f) return -1;
//     char buf[1024];
//     IdxEntry *arr = NULL; size_t cnt = 0, capv = 0;
//     while (fgets(buf,sizeof(buf),f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&ii,&off,lhex,chex)==4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii; arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr; out->count = cnt;
//     return 0;
// }

// /* 找可信前缀（idx 与 log 不一致时） */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_chain) memset(out_chain,0,32);
//         if (out_offset) *out_offset = 0;
//         free_idx_array(&idx);
//         return 0;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32]; memset(H,0,sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0, last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned int mdlen=0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     last_good_i = e->i;
//                     last_good_off = e->off;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else break;
//             } else if (e->i < ln) {
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else break;
//                 } else break;
//             } else break;
//         } else break;
//     }

//     free(line); fclose(lf);
//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);
//     free_idx_array(&idx);
//     if (last_good_i > 0) return 1;
//     return 0;
// }

// /* -------- seal 解析 -------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}

// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S,0,sizeof(*S));
//     FILE *f = fopen(seal_path,"r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line,&cap,f)) != -1) {
//         if (line[0]=='#' || line[0]==0) continue;
//         trimnl(line);
//         char *eq = strchr(line,'=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version,S->hash,S->sign,S->sm2_id,S->path,S->records,S->chain,S->key_fpr,S->index_sm3) < 0) return NULL;
//     return buf;
// }

// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path,"rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line,&cap,fp)) != -1) {
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen = 0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line); fclose(fp); memcpy(out32,H,32); return 0;
// }

// /* ---------- idx handle ---------- */
// static int g_idx_fd = -1;
// static FILE *g_idx = NULL;

// static void close_idx_handle(void) {
//     if (g_idx) { fclose(g_idx); g_idx = NULL; }
//     if (g_idx_fd >= 0) { close(g_idx_fd); g_idx_fd = -1; }
// }
// static int open_idx_handle(void) {
//     close_idx_handle();
//     int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
//     if (fd < 0) {
//         log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     FILE *f = fdopen(dup(fd), "a+");
//     if (!f) { log_msg("open_idx_handle: fdopen failed: %s", strerror(errno)); close(fd); return -1; }
//     g_idx_fd = fd;
//     g_idx = f;
//     fchmod(g_idx_fd, 0600);
//     return 0;
// }
// static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex) {
//     if (g_idx_fd < 0) {
//         log_msg("append_idx_line_atomic: idx fd not open");
//         return -1;
//     }
//     if (flock(g_idx_fd, LOCK_EX) != 0) {
//         log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
//     }
//     int ret = 0;
//     ssize_t w = dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n",
//                         (unsigned long long)i, (unsigned long long)off, lhex, chex);
//     if (w < 0) { log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno)); ret = -1; }
//     else fsync(g_idx_fd);
//     flock(g_idx_fd, LOCK_UN);
//     return ret;
// }
// static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath,
//                                        unsigned char out_chain[32],
//                                        unsigned long long *out_lines,
//                                        unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     unsigned long long ln=0, offset=0;
//     unsigned char H[32]; memset(H,0,sizeof(H));

//     close_idx_handle();

//     char tmppath[PATH_MAX];
//     snprintf(tmppath,sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line,&cap,lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new(); unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL)!=1 || EVP_DigestUpdate(c1, line, normlen)!=1 || EVP_DigestFinal_ex(c1,L,&lm)!=1 || lm!=32) {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new(); unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL)!=1 || EVP_DigestUpdate(c2, H, sizeof(H))!=1 || EVP_DigestUpdate(c2, line, normlen)!=1 || EVP_DigestFinal_ex(c2, newH, &hm)!=1 || hm!=32) {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L,32,lhex,sizeof(lhex));
//         hexdump(H,32,chex,sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line); fclose(lf);
//     fflush(tf); fsync(fileno(tf)); fclose(tf);
//     if (rename(tmppath, idxpath) != 0) { unlink(tmppath); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("rebuild: open_idx_handle failed after rename"); return -1; }
//     if (out_chain) memcpy(out_chain,H,32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }
// static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
// {
//     close_idx_handle();
//     FILE *in = fopen(idxpath,"r");
//     if (!in) {
//         if (keep_i == 0) {
//             FILE *out = fopen(idxpath,"w");
//             if (!out) return -1;
//             fclose(out);
//             chmod(idxpath, 0600);
//             if (open_idx_handle() != 0) return -1;
//             return 0;
//         }
//         return -1;
//     }
//     char tmp[PATH_MAX];
//     snprintf(tmp,sizeof(tmp), "%s.trunc", idxpath);
//     FILE *out = fopen(tmp, "w");
//     if (!out) { fclose(in); return -1; }
//     char buf[2048];
//     while (fgets(buf,sizeof(buf),in)) {
//         if (buf[0]=='#') { if (fputs(buf,out) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; } continue; }
//         unsigned long long i=0, off=0; char lhex[256]={0}, chex[256]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%255s chash=%255s",&i,&off,lhex,chex)==4) {
//             if (i <= keep_i) {
//                 if (fprintf(out,"i=%llu off=%llu lhash=%s chash=%s\n",i,off,lhex,chex) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; }
//             } else break;
//         }
//     }
//     fclose(in);
//     fflush(out); fsync(fileno(out)); fclose(out);
//     if (rename(tmp, idxpath) != 0) { unlink(tmp); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("truncate: open_idx_handle failed after rename"); return -1; }
//     return 0;
// }

// /* ---------- global state ---------- */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static int g_run = 1;
// static int g_degraded_mode = 0;

// static void on_signal(int sig) { if (sig == SIGINT || sig == SIGTERM) g_run = 0; }

// /* NEW: 从 idx 尾恢复 */
// static int recover_tail_from_idx(unsigned long long *out_i,
//                                  unsigned long long *out_off,
//                                  unsigned char out_chain[32])
// {
//     FILE *ix = fopen(IDX_PATH, "r");
//     if (!ix) return -1;
//     char buf[1024]; unsigned long long last_i = 0, last_off = 0;
//     char last_ch[65] = {0};
//     while (fgets(buf, sizeof(buf), ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//             last_i = i; last_off = off; strncpy(last_ch, c, sizeof(last_ch)-1);
//         }
//     }
//     fclose(ix);
//     if (out_i) *out_i = last_i;
//     if (out_off) *out_off = last_off;
//     if (out_chain) {
//         if (last_i == 0) { memset(out_chain,0,32); return 0; }
//         for (int i=0;i<32;i++){
//             unsigned int hi,lo;
//             if (sscanf(&last_ch[i*2], "%1x%1x", &hi,&lo) != 2) return -1;
//             out_chain[i]=(hi<<4)|lo;
//         }
//     }
//     return 0;
// }

// /* NEW: 读取 idx 最后一条的完整 4 元组 */
// static int read_last_idx_entry(IdxEntry *out)
// {
//     if (!out) return -1;
//     memset(out,0,sizeof(*out));
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) return -1;
//     char buf[1024];
//     int have = 0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             out->i = i; out->off = off;
//             strncpy(out->lhash, lhex, sizeof(out->lhash)-1);
//             strncpy(out->chash, chex, sizeof(out->chash)-1);
//             have = 1;
//         }
//     }
//     fclose(ix);
//     return have ? 0 : -1;
// }

// /* NEW: 启动后自检与对齐（解决 3/4 行不一致） */
// static int reconcile_idx_with_log(void)
// {
//     unsigned long long log_lines = count_file_lines_simple(LOG_PATH);
//     if (g_lines > log_lines) {
//         log_msg("reconcile: idx has %llu lines > log %llu, truncating idx",
//                 (unsigned long long)g_lines, (unsigned long long)log_lines);
//         if (truncate_idx_to_i_atomic(IDX_PATH, log_lines) != 0) {
//             log_msg("reconcile: truncate idx to %llu failed", (unsigned long long)log_lines);
//             return -1;
//         }
//         if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//             log_msg("reconcile: recover_tail_from_idx failed after truncate");
//             return -1;
//         }
//     }
//     return 0;
// }

// /* 验证现有 seal（同你版.本，略微格式清理；逻辑不变） */
// /* 验证现有 seal：若任何一致性失败，则回退到可信前缀并进入降级模式（即使找到了可信前缀也降级） */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* 首次无 seal */
//     }

//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal algorithm mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }

//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der; X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* 校验证书指纹与 key_fpr */
//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv,mdlen,fpr_hex,sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }

//     /* 验签 canon */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1; }
//     size_t siglen = 0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal signature invalid");
//         return -1;
//     }

//     unsigned long long N = strtoull(S.records, NULL, 10);

//     /* 1) 复算前 N 行链，与 seal.chain 比较 */
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR,32,chx,sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             log_msg("Existing seal chain mismatch; locating trusted prefix and entering degraded mode");
//             trigger_alarm("Existing seal chain mismatch - degraded mode (manual review required)");

//             unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);

//             /* 无论是否找到可信前缀，均进入降级模式，禁止后续重建和写 seal */
//             g_degraded_mode = 1;

//             if (ret == 1) {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0)
//                     log_msg("truncate idx to trusted_lines failed");
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("trusted prefix: lines=%llu off=%llu (degraded mode ON)",
//                         (unsigned long long)g_lines, (unsigned long long)g_offset);
//             } else {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0)
//                     log_msg("truncate idx to 0 failed");
//                 memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//                 log_msg("no trusted prefix; reset to 0 (degraded mode ON)");
//             }

//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0; /* 已进入降级并回退到可信点 */
//         }
//     }

//     /* 2) idx 文件哈希与 tail 一致性 */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Cannot compute idx sm3"); return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             log_msg("Index file hash mismatch; locating trusted prefix and entering degraded mode");
//             trigger_alarm("Index file hash mismatch - degraded mode (manual review required)");

//             unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);

//             g_degraded_mode = 1;

//             if (ret == 1) {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0)
//                     log_msg("truncate idx to trusted_lines failed");
//                 memcpy(g_chain, trusted_chain, 32); g_lines = trusted_lines; g_offset = trusted_off;
//                 log_msg("trusted prefix after idx mismatch: lines=%llu off=%llu (degraded mode ON)",
//                         (unsigned long long)g_lines, (unsigned long long)g_offset);
//             } else {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0)
//                     log_msg("truncate idx to 0 failed");
//                 memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//                 log_msg("no trusted prefix after idx mismatch; reset to 0 (degraded mode ON)");
//             }

//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }

//         /* tail 检查（idx 最后一项与 seal 尾部） */
//         FILE *ix = fopen(IDX_PATH,"r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65] = {0};
//             while (fgets(buf,sizeof(buf),ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("seal vs idx tail mismatch; locating trusted prefix and entering degraded mode");
//                 trigger_alarm("Seal vs idx tail mismatch - degraded mode (manual review required)");

//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);

//                 g_degraded_mode = 1;

//                 if (ret == 1) {
//                     if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0)
//                         log_msg("truncate idx to trusted_lines failed");
//                     memcpy(g_chain, trusted_chain, 32); g_lines = trusted_lines; g_offset = trusted_off;
//                     log_msg("trusted prefix after tail mismatch: lines=%llu off=%llu (degraded mode ON)",
//                             (unsigned long long)g_lines, (unsigned long long)g_offset);
//                 } else {
//                     if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0)
//                         log_msg("truncate idx to 0 failed");
//                     memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//                     log_msg("no trusted prefix after tail mismatch; reset to 0 (degraded mode ON)");
//                 }

//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* 一切匹配，设置 g_chain/g_lines/g_offset */
//     unsigned long long N2 = strtoull(S.records, NULL, 10);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi, &lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i] = (hi<<4)|lo;
//     }
//     g_lines = N2;

//     FILE *ix2 = fopen(IDX_PATH,"r");
//     if (!ix2) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Cannot open idx for reading offset"); return -1; }
//     char b2[1024]; unsigned long long last_off = 0; unsigned long long last_i = 0;
//     while (fgets(b2,sizeof(b2),ix2)) {
//         if (b2[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(b2,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i = i; last_off = off;
//         }
//     }
//     fclose(ix2);
//     if (last_i != g_lines) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing idx last_i != seal records - inconsistent"); return -1; }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* 处理一行（从 audit.log 读取的内容） */
// static int gmseal_on_line_from_logbuf(const char *buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = raw_n;
//     while (normlen > 0 && (buf[normlen-1]=='\n' || buf[normlen-1]=='\r')) normlen--;

//     unsigned char lhash[32], cand_chain[32];
//     unsigned int lm=0, hm=0;

//     EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//     if (!c1) return -1;
//     if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(c1, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(c1, lhash, &lm) != 1 || lm != 32) {
//         EVP_MD_CTX_free(c1); return -1;
//     }
//     EVP_MD_CTX_free(c1);

//     EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//     if (!c2) return -1;
//     if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(c2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(c2, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(c2, cand_chain, &hm) != 1 || hm != 32) {
//         EVP_MD_CTX_free(c2); return -1;
//     }
//     EVP_MD_CTX_free(c2);

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(cand_chain, 32, chex, sizeof(chex));

//     if (g_idx_fd < 0 && open_idx_handle() != 0) {
//         log_msg("gmseal_on_line: open_idx_handle failed");
//         return -1;
//     }

//     /* 防重复追加（极端触发/并发窗口保护）：如果 idx 尾项已经等于我们要写的 cand_chain，则跳过 */
//     IdxEntry tail = {0};
//     if (read_last_idx_entry(&tail) == 0) {
//         if (tail.i == g_lines && strncmp(tail.chash, chex, sizeof(tail.chash)) == 0) {
//             /* 这说明上一次已经追加过了，而我们的 g_lines 还没来得及更新；直接返回成功 */
//             return 0;
//         }
//     }

//     if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
//         log_msg("gmseal_on_line: append_idx_line_atomic failed for next i=%llu", (unsigned long long)(g_lines + 1));
//         return -1;
//     }

//     memcpy(g_chain, cand_chain, 32);
//     g_lines++;
//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* ---- 写 seal 之前的“硬一致门闸” ---- */
// static int hard_reconcile_all(void)
// {
//     /* 以 audit.log 实际行数为锚点 */
//     unsigned long long log_lines = count_file_lines_simple(LOG_PATH);

//     /* 快速路径：内存视图已一致 */
//     if (log_lines == g_lines) return 0;

//     log_msg("hard_reconcile: log_lines=%llu, g_lines=%llu -> reconciling",
//             (unsigned long long)log_lines, (unsigned long long)g_lines);

//     /* 先软对齐一次 */
//     (void)reconcile_idx_with_log();

//     /* 如果仍不一致，强制重建 idx */
//     unsigned long long L1 = 0, off1 = 0; unsigned char ch1[32];
//     if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, ch1, &L1, &off1) != 0) {
//         log_msg("hard_reconcile: rebuild_idx_from_log_atomic failed");
//         return -1;
//     }
//     if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//         log_msg("hard_reconcile: recover_tail_from_idx failed");
//         return -1;
//     }

//     /* 再看一次是否一致（大多数场景到这里就齐了） */
//     log_lines = count_file_lines_simple(LOG_PATH);
//     if (g_lines == log_lines) return 0;

//     /* 仍不一致（例如 auditd 正在写入但尚未 flush）。这时以 audit.log 为准，强行赋值链与偏移 */
//     unsigned char Hfix[32];
//     if (sm3_chain_file_limit_local(LOG_PATH, log_lines, Hfix) != 0) {
//         log_msg("hard_reconcile: sm3_chain_file_limit_local failed");
//         return -1;
//     }

//     /* truncate/extend idx 到 log_lines */
//     if (truncate_idx_to_i_atomic(IDX_PATH, log_lines) != 0) {
//         log_msg("hard_reconcile: truncate idx to %llu failed", (unsigned long long)log_lines);
//         return -1;
//     }
//     /* 重新读取 idx 尾，拿到 off；若 idx 空且 log_lines>0，前面的 rebuild 已覆盖，这里一般能拿到 */
//     unsigned long long li=0, off=0; unsigned char tmpc[32];
//     if (recover_tail_from_idx(&li, &off, tmpc) != 0) {
//         log_msg("hard_reconcile: recover_tail_from_idx after truncate failed");
//         return -1;
//     }
//     /* 如果 idx 仍为空但 log 有行（极少见），退化 off=0，由后续 on_line 逐步写 */
//     if (log_lines > 0 && li == 0) {
//         off = 0;
//     }

//     memcpy(g_chain, Hfix, 32);
//     g_lines = log_lines;
//     g_offset = off;

//     log_msg("hard_reconcile: forced align to log_lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//     return 0;
// }

// /* 写 seal */
// static int gmseal_write_seal(void)
// {
//     if (g_degraded_mode) {
//         log_msg("In degraded mode: skip writing seal");
//         return 0;
//     }

//     /* 关键：写 seal 前做硬一致门闸 */
//     if (hard_reconcile_all() != 0) {
//         log_msg("gmseal_write_seal: hard_reconcile_all failed, skip this round");
//         return -1;
//     }

//     EVP_PKEY *pkey = NULL; X509 *cert = NULL;
//     FILE *pf = fopen(PASS_PATH,"r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n = getline(&secret, &cap, pf); fclose(pf);
//     if (n <= 0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n]=0;

//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH,"r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf,sizeof(saltbuf),sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen = strlen(saltbuf); while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++){ unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x", &hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; } salt[i] = (hi<<4)|lo; }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived)) {
//         log_msg("PBKDF2-SM3 failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     FILE *pf2 = fopen(P12_PATH,"rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) { log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1; }
//     PKCS12_free(p12);

//     unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     /* 写 seal 之前，再算一次 idx_sm3（与刚才硬对齐后的 idx 一致） */
//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(IDX_PATH, idx_sm3) != 0) { EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed"); return -1; }

//     char *canon = NULL;
//     if (asprintf(&canon, "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
//         SM2_ID, LOG_PATH, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     unsigned char *sig=NULL; size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     char *certb64 = NULL;
//     if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); } else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     EVP_PKEY_free(pkey); X509_free(cert);

//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp), "%s.tmp", SEAL_PATH);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, SEAL_PATH) != 0) { log_msg("rename seal tmp failed: %s", strerror(errno)); unlink(tmp); free(canon); free(sigb64); free(certb64); return -1; }
//     free(canon); free(sigb64); free(certb64);
//     log_msg("seal written: %s (records=%llu)", SEAL_PATH, (unsigned long long)g_lines);
//     return 0;
// }

// /* 读取 audit.log 的 tail 全部吃完（带运行期前置校验） */
// /* 读取 audit.log 的 tail 全部吃完（带运行期前置校验；一旦发现尾项不一致则回退并进入降级） */
// static int process_all_available_lines(void)
// {
//     /* 前置：若已处理过至少 1 行，则核验 idx 尾项指向的那一行内容是否仍一致 */
//     if (g_lines > 0) {
//         IdxEntry tail = {0};
//         if (read_last_idx_entry(&tail) != 0) {
//             log_msg("preflight: cannot read last idx entry");
//             trigger_alarm("Runtime preflight failed: cannot read last idx entry");
//             return -1;
//         }

//         FILE *chk = fopen(LOG_PATH, "rb");
//         if (!chk) {
//             log_msg("preflight: open log failed: %s", strerror(errno));
//             trigger_alarm("Runtime preflight failed: cannot open audit.log");
//             return -1;
//         }
//         if (fseeko(chk, (off_t)tail.off, SEEK_SET) != 0) {
//             log_msg("preflight: fseeko to tail.off=%llu failed: %s",
//                     (unsigned long long)tail.off, strerror(errno));
//             fclose(chk);
//             goto mismatch_and_rollback;
//         }
//         char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, chk);
//         fclose(chk);
//         if (n <= 0) {
//             free(line);
//             log_msg("preflight: cannot read line at off=%llu", (unsigned long long)tail.off);
//             goto mismatch_and_rollback;
//         }
//         size_t norm = normalize_line(line, (size_t)n);
//         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//         unsigned char md[32]; unsigned int mdlen = 0;
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         int ok = ctx && sm3 &&
//                  EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
//                  EVP_DigestUpdate(ctx, line, norm)==1 &&
//                  EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
//         if (ctx) EVP_MD_CTX_free(ctx);
//         char lhex_now[65]={0};
//         if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));
//         free(line);

//         if (!ok || strcmp(lhex_now, tail.lhash) != 0) {
//             log_msg("preflight: last processed line content mismatch (idx lhash=%s, now=%s)",
//                     tail.lhash, lhex_now[0]?lhex_now:"<err>");
//             goto mismatch_and_rollback;
//         }
//         /* 前置校验通过，继续正常处理 */
//     }

//     /* 正常尾读：从 g_offset 开始把 backlog 吃干净 */
//     FILE *lf = fopen(LOG_PATH, "rb");
//     if (!lf) { log_msg("open %s failed: %s", LOG_PATH, strerror(errno)); return -1; }
//     if (fseeko(lf, (off_t)g_offset, SEEK_SET) != 0) {
//         log_msg("fseeko to %llu failed: %s", (unsigned long long)g_offset, strerror(errno));
//         fclose(lf); return -1;
//     }
//     int consumed = 0;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, lf)) > 0) {
//         if (gmseal_on_line_from_logbuf(line, (size_t)n) != 0) {
//             log_msg("process_all_available_lines: on_line failed");
//             free(line); fclose(lf); return -1;
//         }
//         consumed++;
//     }
//     free(line); fclose(lf);
//     return consumed; // 0 表示当前没有新行

// mismatch_and_rollback: ;
//     /* 运行期发现 idx 尾项与当前日志不一致：回退到可信前缀并进入降级模式 */
//     trigger_alarm("Chain mismatch detected at runtime; rolling back to trusted prefix and entering degraded mode");
//     unsigned long long trusted_lines = 0, trusted_off = 0;
//     unsigned char trusted_chain[32]; memset(trusted_chain,0,sizeof(trusted_chain));
//     int ret = find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//     if (ret == 1) {
//         if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) {
//             log_msg("rollback: truncate idx to %llu failed", (unsigned long long)trusted_lines);
//         }
//         memcpy(g_chain, trusted_chain, 32);
//         g_lines = trusted_lines;
//         g_offset = trusted_off;
//         log_msg("rollback: trusted prefix lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//     } else {
//         /* 一个可信前缀都找不到，全部清零 */
//         if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
//             log_msg("rollback: truncate idx to 0 failed");
//         }
//         memset(g_chain,0,sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;
//         log_msg("rollback: no trusted prefix; reset to 0");
//     }
//     g_degraded_mode = 1;  /* 降级：后续不写 seal，只报警 */
//     return 0;             /* 返回 0：调用方可继续循环，但不会产生新的 seal */
// }


// /* 初始化 */
// static int gmseal_init(void)
// {
//     memset(g_chain,0,sizeof(g_chain));
//     g_lines = 0; g_offset = 0; g_degraded_mode = 0;

//     if (open_idx_handle() != 0) log_msg("warning: cannot open idx on init; will attempt later");

//     int vs = verify_existing_seal();
//     if (vs == 0 && g_degraded_mode) {
//        log_msg("degraded mode after verify: skip resync/rebuild; holding at trusted prefix");
//     /* 可选：这里也可以再做一次 reconcile_idx_with_log() 的“只截断不扩展”，
//        但绝不要把 log_lines > g_lines 的情况用 rebuild 去“同化”。 */
//     }
//     if (vs < 0) { log_msg("Existing seal mismatch/verify error - aborting"); return -1; }

//     unsigned long long log_lines = count_file_lines_simple(LOG_PATH);

//     if (vs > 0) { // 首次无 seal
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log_atomic failed (first run)"); return -1;
//         }
//         if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//             log_msg("recover_tail_from_idx failed after first-run rebuild"); return -1;
//         }
//         if (reconcile_idx_with_log() != 0) return -1;
//         log_msg("first-run: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//     } else if(!g_degraded_mode){
//         if (log_lines > g_lines) {
//             log_msg("log has %llu > seal.records %llu -> resync", (unsigned long long)log_lines, (unsigned long long)g_lines);
//             unsigned char dummy[32]; unsigned long long nl=0, noff=0;
//             if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, dummy, &nl, &noff) != 0) {
//                 log_msg("rebuild_idx_from_log_atomic failed (resync)"); return -1;
//             }
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed after resync"); return -1;
//             }
//             if (reconcile_idx_with_log() != 0) return -1;
//             log_msg("resync: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         } else if (log_lines < g_lines) {
//             log_msg("log truncated: %llu < %llu, entering degraded & truncate idx", (unsigned long long)log_lines, (unsigned long long)g_lines);
//             g_degraded_mode = 1;
//             if (truncate_idx_to_i_atomic(IDX_PATH, log_lines) != 0) log_msg("truncate idx to %llu failed", (unsigned long long)log_lines);
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed after truncate"); return -1;
//             }
//             log_msg("truncate sync: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         } else {
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed (in sync)"); return -1;
//             }
//             log_msg("verified & in sync: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         }
//     }
//     return 0;
// }

// /* stdin 驱动：每次触发都把 backlog 吃干净，并（可选）写 seal */
// static int stdin_event_loop(void)
// {
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (g_run && (n = getline(&line, &cap, stdin)) != -1) {
//         (void)n; // stdin 内容不使用，仅作触发
//         int rc = process_all_available_lines();
//         if (rc < 0) {
//             trigger_alarm("gmseal plugin processing error - exiting");
//             free(line); return -1;
//         }
//         if (ALWAYS_REFRESH_SEAL && rc > 0) {
//             if (gmseal_write_seal() != 0) log_msg("gmseal_write_seal failed");
//         }
//     }
//     if (line) free(line);
//     return 0;
// }

// /* main */
// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (acquire_singleton_lock() != 0) {
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 0; /* 非致命：另一实例在跑，我们退出 */
//     }

//     if (gm_crypto_init() != 0) {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         release_singleton_lock();
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0) {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         release_singleton_lock();
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 2;
//     }

//     /* 启动后先把当前可读尾部吃完，再写首次 seal，避免初始化窗口的不一致 */
//     {
//         int ate = process_all_available_lines();
//         if (ate < 0) {
//             log_msg("initial tail scan failed");
//         } else {
//             if (reconcile_idx_with_log() != 0) {
//                 log_msg("reconcile after initial scan failed");
//             }
//         }
//     }
//     /* 首次 seal 也走硬一致门闸 */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* 进入主循环 */
//     if (stdin_event_loop() != 0) {
//         log_msg("stdin_event_loop aborted due to errors");
//     }

//     /* 结束前 seal（仍然先硬对齐） */
//     (void)gmseal_write_seal();

//     close_idx_handle();
//     gm_crypto_cleanup();
//     release_singleton_lock();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }



//版本  2、行不一致问题解决
//(行一致，但是发生篡改后，再出发审计日志，就会强制覆盖，导致检查不出来哪一行被篡改)
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>
// #include <sys/time.h>
// #include <dirent.h>
// #include <sys/sysmacros.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可改成周期） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// /* singleton lock */
// #define RUN_LOCK "/var/run/audisp-gmseal.lock"

// static FILE *g_log = NULL;
// static int g_run_lock_fd = -1;

// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log) return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }
// static int acquire_singleton_lock(void)
// {
//     g_run_lock_fd = open(RUN_LOCK, O_CREAT|O_RDWR, 0644);
//     if (g_run_lock_fd < 0) {
//         log_msg("open run lock %s failed: %s", RUN_LOCK, strerror(errno));
//         return -1;
//     }
//     if (flock(g_run_lock_fd, LOCK_EX | LOCK_NB) != 0) {
//         log_msg("another gmseal instance is running (lock busy), exiting");
//         return -1;
//     }
//     dprintf(g_run_lock_fd, "%ld\n", (long)getpid());
//     return 0;
// }
// static void release_singleton_lock(void)
// {
//     if (g_run_lock_fd >= 0) {
//         flock(g_run_lock_fd, LOCK_UN);
//         close(g_run_lock_fd);
//         unlink(RUN_LOCK); /* 容忍失败 */
//         g_run_lock_fd = -1;
//     }
// }

// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) { if (b64) BIO_free(b64); if (mem) BIO_free(mem); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) { BIO_free_all(b64); return NULL; }
//     (void)BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) len--;
//     return len;
// }

// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL; size_t cap = 0; ssize_t n; unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line); fclose(f); return ln;
// }

// /* ------------ idx helpers ------------ */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL; a->count = 0;
// }

// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out,0,sizeof(*out));
//     FILE *f = fopen(path,"r");
//     if (!f) return -1;
//     char buf[1024];
//     IdxEntry *arr = NULL; size_t cnt = 0, capv = 0;
//     while (fgets(buf,sizeof(buf),f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&ii,&off,lhex,chex)==4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii; arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr; out->count = cnt;
//     return 0;
// }

// /* 找可信前缀（idx 与 log 不一致时） */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_chain) memset(out_chain,0,32);
//         if (out_offset) *out_offset = 0;
//         free_idx_array(&idx);
//         return 0;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32]; memset(H,0,sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0, last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned int mdlen=0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     last_good_i = e->i;
//                     last_good_off = e->off;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else break;
//             } else if (e->i < ln) {
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else break;
//                 } else break;
//             } else break;
//         } else break;
//     }

//     free(line); fclose(lf);
//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);
//     free_idx_array(&idx);
//     if (last_good_i > 0) return 1;
//     return 0;
// }

// /* -------- seal 解析 -------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}

// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S,0,sizeof(*S));
//     FILE *f = fopen(seal_path,"r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line,&cap,f)) != -1) {
//         if (line[0]=='#' || line[0]==0) continue;
//         trimnl(line);
//         char *eq = strchr(line,'=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version,S->hash,S->sign,S->sm2_id,S->path,S->records,S->chain,S->key_fpr,S->index_sm3) < 0) return NULL;
//     return buf;
// }

// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path,"rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line,&cap,fp)) != -1) {
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen = 0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line); fclose(fp); memcpy(out32,H,32); return 0;
// }

// /* ---------- idx handle ---------- */
// static int g_idx_fd = -1;
// static FILE *g_idx = NULL;

// static void close_idx_handle(void) {
//     if (g_idx) { fclose(g_idx); g_idx = NULL; }
//     if (g_idx_fd >= 0) { close(g_idx_fd); g_idx_fd = -1; }
// }
// static int open_idx_handle(void) {
//     close_idx_handle();
//     int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
//     if (fd < 0) {
//         log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     FILE *f = fdopen(dup(fd), "a+");
//     if (!f) { log_msg("open_idx_handle: fdopen failed: %s", strerror(errno)); close(fd); return -1; }
//     g_idx_fd = fd;
//     g_idx = f;
//     fchmod(g_idx_fd, 0600);
//     return 0;
// }
// static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex) {
//     if (g_idx_fd < 0) {
//         log_msg("append_idx_line_atomic: idx fd not open");
//         return -1;
//     }
//     if (flock(g_idx_fd, LOCK_EX) != 0) {
//         log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
//     }
//     int ret = 0;
//     ssize_t w = dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n",
//                         (unsigned long long)i, (unsigned long long)off, lhex, chex);
//     if (w < 0) { log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno)); ret = -1; }
//     else fsync(g_idx_fd);
//     flock(g_idx_fd, LOCK_UN);
//     return ret;
// }
// static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath,
//                                        unsigned char out_chain[32],
//                                        unsigned long long *out_lines,
//                                        unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     unsigned long long ln=0, offset=0;
//     unsigned char H[32]; memset(H,0,sizeof(H));

//     close_idx_handle();

//     char tmppath[PATH_MAX];
//     snprintf(tmppath,sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line,&cap,lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new(); unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL)!=1 || EVP_DigestUpdate(c1, line, normlen)!=1 || EVP_DigestFinal_ex(c1,L,&lm)!=1 || lm!=32) {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new(); unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL)!=1 || EVP_DigestUpdate(c2, H, sizeof(H))!=1 || EVP_DigestUpdate(c2, line, normlen)!=1 || EVP_DigestFinal_ex(c2, newH, &hm)!=1 || hm!=32) {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L,32,lhex,sizeof(lhex));
//         hexdump(H,32,chex,sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line); fclose(lf);
//     fflush(tf); fsync(fileno(tf)); fclose(tf);
//     if (rename(tmppath, idxpath) != 0) { unlink(tmppath); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("rebuild: open_idx_handle failed after rename"); return -1; }
//     if (out_chain) memcpy(out_chain,H,32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }
// static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
// {
//     close_idx_handle();
//     FILE *in = fopen(idxpath,"r");
//     if (!in) {
//         if (keep_i == 0) {
//             FILE *out = fopen(idxpath,"w");
//             if (!out) return -1;
//             fclose(out);
//             chmod(idxpath, 0600);
//             if (open_idx_handle() != 0) return -1;
//             return 0;
//         }
//         return -1;
//     }
//     char tmp[PATH_MAX];
//     snprintf(tmp,sizeof(tmp), "%s.trunc", idxpath);
//     FILE *out = fopen(tmp, "w");
//     if (!out) { fclose(in); return -1; }
//     char buf[2048];
//     while (fgets(buf,sizeof(buf),in)) {
//         if (buf[0]=='#') { if (fputs(buf,out) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; } continue; }
//         unsigned long long i=0, off=0; char lhex[256]={0}, chex[256]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%255s chash=%255s",&i,&off,lhex,chex)==4) {
//             if (i <= keep_i) {
//                 if (fprintf(out,"i=%llu off=%llu lhash=%s chash=%s\n",i,off,lhex,chex) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; }
//             } else break;
//         }
//     }
//     fclose(in);
//     fflush(out); fsync(fileno(out)); fclose(out);
//     if (rename(tmp, idxpath) != 0) { unlink(tmp); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("truncate: open_idx_handle failed after rename"); return -1; }
//     return 0;
// }

// /* ---------- global state ---------- */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static int g_run = 1;
// static int g_degraded_mode = 0;

// /* 持久持有的 audit.log 句柄与 inode，用于轮转检测 */
// static int   g_log_fd  = -1;
// static FILE *g_log_fp  = NULL;
// static dev_t g_log_dev = 0;
// static ino_t g_log_ino = 0;

// static void on_signal(int sig) { if (sig == SIGINT || sig == SIGTERM) g_run = 0; }

// /* NEW: 打开/关闭当前 log 的句柄（持久） */
// static int open_log_handle(void)
// {
//     if (g_log_fd >= 0) return 0;
//     g_log_fd = open(LOG_PATH, O_RDONLY | O_CLOEXEC);
//     if (g_log_fd < 0) {
//         log_msg("open_log_handle: open %s failed: %s", LOG_PATH, strerror(errno));
//         return -1;
//     }
//     struct stat st;
//     if (fstat(g_log_fd, &st) != 0) {
//         log_msg("open_log_handle: fstat failed: %s", strerror(errno));
//         close(g_log_fd); g_log_fd = -1;
//         return -1;
//     }
//     g_log_dev = st.st_dev;
//     g_log_ino = st.st_ino;
//     g_log_fp = fdopen(dup(g_log_fd), "rb");
//     if (!g_log_fp) {
//         log_msg("open_log_handle: fdopen failed: %s", strerror(errno));
//         close(g_log_fd); g_log_fd = -1;
//         return -1;
//     }
//     return 0;
// }
// static void close_log_handle(void)
// {
//     if (g_log_fp) { fclose(g_log_fp); g_log_fp = NULL; }
//     if (g_log_fd >= 0) { close(g_log_fd); g_log_fd = -1; }
//     g_log_dev = 0; g_log_ino = 0;
// }

// /* NEW: 从 idx 尾恢复 */
// static int recover_tail_from_idx(unsigned long long *out_i,
//                                  unsigned long long *out_off,
//                                  unsigned char out_chain[32])
// {
//     FILE *ix = fopen(IDX_PATH, "r");
//     if (!ix) return -1;
//     char buf[1024]; unsigned long long last_i = 0, last_off = 0;
//     char last_ch[65] = {0};
//     while (fgets(buf, sizeof(buf), ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//             last_i = i; last_off = off; strncpy(last_ch, c, sizeof(last_ch)-1);
//         }
//     }
//     fclose(ix);
//     if (out_i) *out_i = last_i;
//     if (out_off) *out_off = last_off;
//     if (out_chain) {
//         if (last_i == 0) { memset(out_chain,0,32); return 0; }
//         for (int i=0;i<32;i++){
//             unsigned int hi,lo;
//             if (sscanf(&last_ch[i*2], "%1x%1x", &hi,&lo) != 2) return -1;
//             out_chain[i]=(hi<<4)|lo;
//         }
//     }
//     return 0;
// }

// /* NEW: 读取 idx 最后一条的完整 4 元组 */
// static int read_last_idx_entry(IdxEntry *out)
// {
//     if (!out) return -1;
//     memset(out,0,sizeof(*out));
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) return -1;
//     char buf[1024];
//     int have = 0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             out->i = i; out->off = off;
//             strncpy(out->lhash, lhex, sizeof(out->lhash)-1);
//             strncpy(out->chash, chex, sizeof(out->chash)-1);
//             have = 1;
//         }
//     }
//     fclose(ix);
//     return have ? 0 : -1;
// }

// /* NEW: 启动后自检与对齐（解决 3/4 行不一致） */
// static int reconcile_idx_with_log(void)
// {
//     unsigned long long log_lines = count_file_lines_simple(LOG_PATH);
//     if (g_lines > log_lines) {
//         log_msg("reconcile: idx has %llu lines > log %llu, truncating idx",
//                 (unsigned long long)g_lines, (unsigned long long)log_lines);
//         if (truncate_idx_to_i_atomic(IDX_PATH, log_lines) != 0) {
//             log_msg("reconcile: truncate idx to %llu failed", (unsigned long long)log_lines);
//             return -1;
//         }
//         if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//             log_msg("reconcile: recover_tail_from_idx failed after truncate");
//             return -1;
//         }
//     }
//     return 0;
// }

// /* NEW: 用 inode 在 /var/log/audit 查找已轮转的旧文件名 */
// static int find_rotated_name_by_inode(char out_path[PATH_MAX], dev_t dev, ino_t ino)
// {
//     DIR *d = opendir("/var/log/audit");
//     if (!d) return -1;
//     struct dirent *de;
//     struct stat st;
//     while ((de = readdir(d))) {
//         if (strncmp(de->d_name, "audit.log", 9) != 0) continue;   // audit.log 或 audit.log.*
//         char cand[PATH_MAX];
//         snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
//         if (stat(cand, &st) != 0) continue;
//         if (st.st_dev == dev && st.st_ino == ino) {
//             strncpy(out_path, cand, PATH_MAX-1);
//             out_path[PATH_MAX-1] = 0;
//             closedir(d);
//             return 0;
//         }
//     }
//     closedir(d);
//     return -1;
// }

// /* ---- 写 seal 的通用函数：为指定 log/idx 输出到指定 seal 文件 ---- */
// static int gmseal_write_seal_for(const char *log_path_for_canon,
//                                  const char *idx_path_for_hash,
//                                  const char *seal_out_path)
// {
//     if (g_degraded_mode) {
//         log_msg("In degraded mode: skip writing seal_for(%s)", log_path_for_canon);
//         return 0;
//     }

//     /* 这里不做 hard_reconcile_all（由调用方在当前文件上下文中保证），只负责输出 */
//     EVP_PKEY *pkey = NULL; X509 *cert = NULL;

//     FILE *pf = fopen(PASS_PATH,"r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n = getline(&secret, &cap, pf); fclose(pf);
//     if (n <= 0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n]=0;

//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH,"r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf,sizeof(saltbuf),sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen = strlen(saltbuf); while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++){ unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x", &hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; } salt[i] = (hi<<4)|lo; }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived)) {
//         log_msg("PBKDF2-SM3 failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     FILE *pf2 = fopen(P12_PATH,"rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) { log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1; }
//     PKCS12_free(p12);

//     /* 证书指纹（SM3） */
//     unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     /* 当前链 */
//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     /* idx 文件 hash（用传入的 idx 路径） */
//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(idx_path_for_hash, idx_sm3) != 0) { EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed for %s", idx_path_for_hash); return -1; }

//     /* 规范文本 */
//     char *canon = NULL;
//     if (asprintf(&canon, "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
//         SM2_ID, log_path_for_canon, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     /* 签名 */
//     unsigned char *sig=NULL; size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     char *certb64 = NULL;
//     if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); } else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     EVP_PKEY_free(pkey); X509_free(cert);

//     /* 输出到目标 seal */
//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp), "%s.tmp", seal_out_path);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, seal_out_path) != 0) { log_msg("rename seal tmp -> %s failed: %s", seal_out_path, strerror(errno)); unlink(tmp); free(canon); free(sigb64); free(certb64); return -1; }
//     free(canon); free(sigb64); free(certb64);

//     log_msg("seal written: %s (records=%llu path=%s)", seal_out_path, (unsigned long long)g_lines, log_path_for_canon);
//     return 0;
// }

// /* ---- 写 seal（当前文件：会先做硬一致门闸） ---- */
// static int hard_reconcile_all(void)
// {
//     /* 以 audit.log 实际行数为锚点 */
//     unsigned long long log_lines = count_file_lines_simple(LOG_PATH);

//     /* 快速路径：内存视图已一致 */
//     if (log_lines == g_lines) return 0;

//     log_msg("hard_reconcile: log_lines=%llu, g_lines=%llu -> reconciling",
//             (unsigned long long)log_lines, (unsigned long long)g_lines);

//     /* 先软对齐一次 */
//     (void)reconcile_idx_with_log();

//     /* 如果仍不一致，强制重建 idx */
//     unsigned long long L1 = 0, off1 = 0; unsigned char ch1[32];
//     if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, ch1, &L1, &off1) != 0) {
//         log_msg("hard_reconcile: rebuild_idx_from_log_atomic failed");
//         return -1;
//     }
//     if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//         log_msg("hard_reconcile: recover_tail_from_idx failed");
//         return -1;
//     }

//     /* 再看一次是否一致（大多数场景到这里就齐了） */
//     log_lines = count_file_lines_simple(LOG_PATH);
//     if (g_lines == log_lines) return 0;

//     /* 仍不一致（例如 auditd 正在写入但尚未 flush）。这时以 audit.log 为准，强行赋值链与偏移 */
//     unsigned char Hfix[32];
//     if (sm3_chain_file_limit_local(LOG_PATH, log_lines, Hfix) != 0) {
//         log_msg("hard_reconcile: sm3_chain_file_limit_local failed");
//         return -1;
//     }

//     /* truncate/extend idx 到 log_lines */
//     if (truncate_idx_to_i_atomic(IDX_PATH, log_lines) != 0) {
//         log_msg("hard_reconcile: truncate idx to %llu failed", (unsigned long long)log_lines);
//         return -1;
//     }
//     /* 取 off */
//     unsigned long long li=0, off=0; unsigned char tmpc[32];
//     if (recover_tail_from_idx(&li, &off, tmpc) != 0) {
//         log_msg("hard_reconcile: recover_tail_from_idx after truncate failed");
//         return -1;
//     }
//     if (log_lines > 0 && li == 0) {
//         off = 0;
//     }

//     memcpy(g_chain, Hfix, 32);
//     g_lines = log_lines;
//     g_offset = off;

//     log_msg("hard_reconcile: forced align to log_lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//     return 0;
// }

// static int gmseal_write_seal(void)
// {
//     if (g_degraded_mode) {
//         log_msg("In degraded mode: skip writing seal");
//         return 0;
//     }
//     if (hard_reconcile_all() != 0) {
//         log_msg("gmseal_write_seal: hard_reconcile_all failed, skip this round");
//         return -1;
//     }
//     return gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
// }

// /* NEW: 旧文件 drain 到 EOF */
// static int drain_old_file_to_eof(void)
// {
//     if (!g_log_fp) return 0;
//     if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
//         log_msg("drain_old_file_to_eof: fseeko failed: %s", strerror(errno));
//         return -1;
//     }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     int cnt = 0;
//     while ((n = getline(&line, &cap, g_log_fp)) > 0) {
//         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//         if (!sm3) { free(line); return -1; }
//         /* 与常规 on_line 相同地追加到 idx 与链 */
//         size_t normlen = normalize_line(line,(size_t)n);

//         unsigned char lhash[32], cand_chain[32];
//         unsigned int lm=0, hm=0;

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         if (!c1) { free(line); return -1; }
//         if (EVP_DigestInit_ex(c1, sm3, NULL)!=1 ||
//             EVP_DigestUpdate(c1, line, normlen)!=1 ||
//             EVP_DigestFinal_ex(c1, lhash, &lm)!=1 || lm!=32) {
//             EVP_MD_CTX_free(c1); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         if (!c2) { free(line); return -1; }
//         if (EVP_DigestInit_ex(c2, sm3, NULL)!=1 ||
//             EVP_DigestUpdate(c2, g_chain, sizeof(g_chain))!=1 ||
//             EVP_DigestUpdate(c2, line, normlen)!=1 ||
//             EVP_DigestFinal_ex(c2, cand_chain, &hm)!=1 || hm!=32) {
//             EVP_MD_CTX_free(c2); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);

//         char lhex[65], chex[65];
//         hexdump(lhash,32,lhex,sizeof(lhex));
//         hexdump(cand_chain,32,chex,sizeof(chex));

//         if (g_idx_fd < 0 && open_idx_handle() != 0) {
//             log_msg("drain_old_file_to_eof: open_idx_handle failed");
//             free(line); return -1;
//         }
//         if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
//             log_msg("drain_old_file_to_eof: append idx failed");
//             free(line); return -1;
//         }
//         memcpy(g_chain, cand_chain, 32);
//         g_lines++;
//         g_offset += (unsigned long long)n;
//         cnt++;
//     }
//     free(line);
//     return cnt;
// }

// /* NEW: 把当前 .idx/.seal 归档到已轮转的旧文件名 */
// static int archive_current_idx_and_seal_to(const char *archived_log_path)
// {
//     char idx_dst[PATH_MAX], seal_dst[PATH_MAX];
//     snprintf(idx_dst,  sizeof(idx_dst),  "%s.idx",  archived_log_path);
//     snprintf(seal_dst, sizeof(seal_dst), "%s.seal", archived_log_path);

//     /* 先关闭 idx 句柄，避免占用 */
//     close_idx_handle();

//     /* 把当前 audit.log.idx 改名为 旧名.idx */
//     if (rename(IDX_PATH, idx_dst) != 0) {
//         log_msg("archive: rename %s -> %s failed: %s", IDX_PATH, idx_dst, strerror(errno));
//         return -1;
//     }
//     chmod(idx_dst, 0600);

//     /* 为旧文件写一份“路径正确”的 seal 到 旧名.seal */
//     if (gmseal_write_seal_for(archived_log_path, idx_dst, seal_dst) != 0) {
//         log_msg("archive: write seal_for(%s) failed", archived_log_path);
//         return -1;
//     }
//     return 0;
// }

// /* NEW: 检测轮转并执行“吃完旧尾巴→归档→切换新文件” */
// static int check_rotation_and_rollover(void)
// {
//     if (g_log_fd < 0) return open_log_handle(); // 第一次打开

//     struct stat st_now;
//     if (stat(LOG_PATH, &st_now) != 0) {
//         // LOG_PATH 暂不可见，可能尚未创建；先不动
//         return 0;
//     }
//     if (st_now.st_dev == g_log_dev && st_now.st_ino == g_log_ino) return 0; // inode 未变

//     // === 轮转发生 ===
//     log_msg("detected rotation: old ino=%ju -> new ino=%ju",
//             (uintmax_t)g_log_ino, (uintmax_t)st_now.st_ino);

//     // 1) 先把旧文件（旧 FD）尾部吃干净
//     (void)drain_old_file_to_eof();

//     // 2) 定位旧文件的新名字
//     char archived_log[PATH_MAX]={0};
//     if (find_rotated_name_by_inode(archived_log, g_log_dev, g_log_ino) != 0) {
//         log_msg("rotation: cannot locate archived filename by inode; will keep current idx/seal names for this generation (less ideal)");
//     } else {
//         // 3) 归档当前 .idx 到 旧名.idx，并为旧名写“路径正确”的 seal
//         (void)archive_current_idx_and_seal_to(archived_log);
//     }

//     // 4) 切换到新文件
//     close_log_handle();
//     if (open_log_handle() != 0) {
//         log_msg("rotation: open new audit.log failed");
//         return -1;
//     }

//     // 5) 新文件从 0 开始
//     if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
//         log_msg("rotation: truncate new idx failed");
//         return -1;
//     }
//     memset(g_chain,0,sizeof(g_chain));
//     g_lines = 0;
//     g_offset = 0;

//     // 6) 清除降级（旧文件的问题已归档，不阻断新链的生成）
//     g_degraded_mode = 0;

//     // 7) 写一版“空链”的新 seal（方便冷启动校准）
//     (void)gmseal_write_seal();

//     return 0;
// }

// /* 验证现有 seal；若发现其实是上一代文件的封印，尝试“归位”自愈 */
// static int try_rehome_stale_seal_to_rotated_file(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) return -1;
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) return -1;
//     unsigned long long N = strtoull(S.records ? S.records : "0", NULL, 10);
//     if (N == 0 || !S.chain) { free_seal(&S); return -1; }

//     DIR *d = opendir("/var/log/audit"); if (!d) { free_seal(&S); return -1; }
//     struct dirent *de; int ok = -1;
//     while ((de = readdir(d))) {
//         if (strncmp(de->d_name, "audit.log.", 10) != 0) continue; // 只看已轮转出去的
//         char cand[PATH_MAX]; snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
//         unsigned char chainR[32]; if (sm3_chain_file_limit_local(cand, N, chainR) != 0) continue;
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) == 0) {
//             // 命中：把 audit.log.idx 改名为 cand.idx，并为 cand 写路径正确的 seal
//             (void)archive_current_idx_and_seal_to(cand);
//             ok = 0; break;
//         }
//     }
//     closedir(d); free_seal(&S);
//     if (ok == 0) {
//         // 给新文件清空 idx/seal，从 0 开始
//         (void)truncate_idx_to_i_atomic(IDX_PATH, 0);
//         memset(g_chain,0,sizeof(g_chain)); g_lines=0; g_offset=0;
//         g_degraded_mode = 0;
//         (void)gmseal_write_seal();
//     }
//     return ok;
// }

// /* 验证现有 seal：若任何一致性失败 -> 先尝试“归位自愈”；仍失败则回退到可信前缀并进入降级 */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* 首次无 seal */
//     }

//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         return -1;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal algorithm mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         free_seal(&S);
//         return -1;
//     }

//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); return -1; }
//     const unsigned char *p = cert_der; X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); return -1; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); return -1; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         return -1;
//     }

//     /* 校验证书指纹与 key_fpr */
//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed"); return -1;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv,mdlen,fpr_hex,sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         return -1;
//     }

//     /* 验签 canon */
//     char *canon = build_canon_from_seal(&S);
//     if (!canon) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); return -1; }
//     size_t siglen = 0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); return -1; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal signature invalid");
//         return -1;
//     }

//     unsigned long long N = strtoull(S.records, NULL, 10);

//     /* 1) 复算前 N 行链，与 seal.chain 比较 */
//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             return -1;
//         }
//         char chx[65]; hexdump(chainR,32,chx,sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             /* 先尝试“归位自愈”（典型于 seal 指向上一代日志） */
//             log_msg("Existing seal chain mismatch; trying to rehome stale seal...");
//             if (try_rehome_stale_seal_to_rotated_file() == 0) {
//                 /* 已把旧 seal/idx 迁回老文件，当前新文件当作首次运行 */
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 log_msg("stale seal rehomed to rotated file; treating as first-run for current log");
//                 return 1;
//             }

//             /* 自愈失败 -> 降级并回退到可信前缀 */
//             log_msg("Existing seal chain mismatch; locating trusted prefix and entering degraded mode");
//             trigger_alarm("Existing seal chain mismatch - degraded mode (manual review required)");

//             unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);

//             g_degraded_mode = 1;

//             if (ret == 1) {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0)
//                     log_msg("truncate idx to trusted_lines failed");
//                 memcpy(g_chain, trusted_chain, 32);
//                 g_lines = trusted_lines;
//                 g_offset = trusted_off;
//                 log_msg("trusted prefix: lines=%llu off=%llu (degraded mode ON)",
//                         (unsigned long long)g_lines, (unsigned long long)g_offset);
//             } else {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0)
//                     log_msg("truncate idx to 0 failed");
//                 memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//                 log_msg("no trusted prefix; reset to 0 (degraded mode ON)");
//             }

//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0; /* 已进入降级并回退到可信点 */
//         }
//     }

//     /* 2) idx 文件哈希与 tail 一致性 */
//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Cannot compute idx sm3"); return -1;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             /* 尝试归位自愈 */
//             log_msg("Index file hash mismatch; trying to rehome stale seal...");
//             if (try_rehome_stale_seal_to_rotated_file() == 0) {
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 log_msg("stale seal rehomed; treating as first-run");
//                 return 1;
//             }

//             log_msg("Index file hash mismatch; locating trusted prefix and entering degraded mode");
//             trigger_alarm("Index file hash mismatch - degraded mode (manual review required)");

//             unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//             memset(trusted_chain,0,sizeof(trusted_chain));
//             int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);

//             g_degraded_mode = 1;

//             if (ret == 1) {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0)
//                     log_msg("truncate idx to trusted_lines failed");
//                 memcpy(g_chain, trusted_chain, 32); g_lines = trusted_lines; g_offset = trusted_off;
//                 log_msg("trusted prefix after idx mismatch: lines=%llu off=%llu (degraded mode ON)",
//                         (unsigned long long)g_lines, (unsigned long long)g_offset);
//             } else {
//                 if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0)
//                     log_msg("truncate idx to 0 failed");
//                 memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//                 log_msg("no trusted prefix after idx mismatch; reset to 0 (degraded mode ON)");
//             }

//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }

//         /* tail 检查（idx 最后一项与 seal 尾部） */
//         FILE *ix = fopen(IDX_PATH,"r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65] = {0};
//             while (fgets(buf,sizeof(buf),ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 /* 尝试归位自愈 */
//                 log_msg("seal vs idx tail mismatch; trying to rehome stale seal...");
//                 if (try_rehome_stale_seal_to_rotated_file() == 0) {
//                     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                     log_msg("stale seal rehomed; treating as first-run");
//                     return 1;
//                 }

//                 log_msg("seal vs idx tail mismatch; locating trusted prefix and entering degraded mode");
//                 trigger_alarm("Seal vs idx tail mismatch - degraded mode (manual review required)");

//                 unsigned long long trusted_lines = 0; unsigned char trusted_chain[32]; unsigned long long trusted_off = 0;
//                 memset(trusted_chain,0,sizeof(trusted_chain));
//                 int ret = find_trusted_prefix(S.path, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);

//                 g_degraded_mode = 1;

//                 if (ret == 1) {
//                     if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0)
//                         log_msg("truncate idx to trusted_lines failed");
//                     memcpy(g_chain, trusted_chain, 32); g_lines = trusted_lines; g_offset = trusted_off;
//                     log_msg("trusted prefix after tail mismatch: lines=%llu off=%llu (degraded mode ON)",
//                             (unsigned long long)g_lines, (unsigned long long)g_offset);
//                 } else {
//                     if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0)
//                         log_msg("truncate idx to 0 failed");
//                     memset(g_chain,0,sizeof(g_chain)); g_lines = 0; g_offset = 0;
//                     log_msg("no trusted prefix after tail mismatch; reset to 0 (degraded mode ON)");
//                 }

//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             return -1;
//         }
//     } else {
//         /* idx 不存在：先尝试归位 */
//         if (try_rehome_stale_seal_to_rotated_file() == 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             log_msg("stale seal rehomed; treating as first-run");
//             return 1;
//         }
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         return -1;
//     }

//     /* 一切匹配，设置 g_chain/g_lines/g_offset */
//     unsigned long long N2 = strtoull(S.records, NULL, 10);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi, &lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             return -1;
//         }
//         g_chain[i] = (hi<<4)|lo;
//     }
//     g_lines = N2;

//     FILE *ix2 = fopen(IDX_PATH,"r");
//     if (!ix2) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Cannot open idx for reading offset"); return -1; }
//     char b2[1024]; unsigned long long last_off = 0; unsigned long long last_i = 0;
//     while (fgets(b2,sizeof(b2),ix2)) {
//         if (b2[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(b2,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i = i; last_off = off;
//         }
//     }
//     fclose(ix2);
//     if (last_i != g_lines) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing idx last_i != seal records - inconsistent"); return -1; }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* 处理一行（从 audit.log 读取的内容） */
// static int gmseal_on_line_from_logbuf(const char *buf, size_t raw_n)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = raw_n;
//     while (normlen > 0 && (buf[normlen-1]=='\n' || buf[normlen-1]=='\r')) normlen--;

//     unsigned char lhash[32], cand_chain[32];
//     unsigned int lm=0, hm=0;

//     EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//     if (!c1) return -1;
//     if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(c1, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(c1, lhash, &lm) != 1 || lm != 32) {
//         EVP_MD_CTX_free(c1); return -1;
//     }
//     EVP_MD_CTX_free(c1);

//     EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//     if (!c2) return -1;
//     if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(c2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(c2, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(c2, cand_chain, &hm) != 1 || hm != 32) {
//         EVP_MD_CTX_free(c2); return -1;
//     }
//     EVP_MD_CTX_free(c2);

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(cand_chain, 32, chex, sizeof(chex));

//     if (g_idx_fd < 0 && open_idx_handle() != 0) {
//         log_msg("gmseal_on_line: open_idx_handle failed");
//         return -1;
//     }

//     /* 防重复追加：如果 idx 尾项已经等于我们要写的 cand_chain，则跳过 */
//     IdxEntry tail = {0};
//     if (read_last_idx_entry(&tail) == 0) {
//         if (tail.i == g_lines && strncmp(tail.chash, chex, sizeof(tail.chash)) == 0) {
//             return 0;
//         }
//     }

//     if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
//         log_msg("gmseal_on_line: append_idx_line_atomic failed for next i=%llu", (unsigned long long)(g_lines + 1));
//         return -1;
//     }

//     memcpy(g_chain, cand_chain, 32);
//     g_lines++;
//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* 读取 audit.log 的 tail 全部吃完（带运行期前置校验；一旦发现尾项不一致则回退并进入降级） */
// static int process_all_available_lines(void)
// {
//     /* 确保持有当前文件 FD（可能在启动时尚未打开） */
//     if (!g_log_fp && open_log_handle() != 0) return -1;

//     /* 前置：若已处理过至少 1 行，则核验 idx 尾项指向的那一行内容是否仍一致（用持久 FD） */
//     if (g_lines > 0) {
//         IdxEntry tail = {0};
//         if (read_last_idx_entry(&tail) != 0) {
//             log_msg("preflight: cannot read last idx entry");
//             trigger_alarm("Runtime preflight failed: cannot read last idx entry");
//             return -1;
//         }
//         if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) {
//             log_msg("preflight: fseeko to tail.off=%llu failed: %s",
//                     (unsigned long long)tail.off, strerror(errno));
//             goto mismatch_and_rollback;
//         }
//         char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
//         if (n <= 0) {
//             free(line);
//             log_msg("preflight: cannot read line at off=%llu", (unsigned long long)tail.off);
//             goto mismatch_and_rollback;
//         }
//         size_t norm = normalize_line(line, (size_t)n);
//         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//         unsigned char md[32]; unsigned int mdlen = 0;
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         int ok = ctx && sm3 &&
//                  EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
//                  EVP_DigestUpdate(ctx, line, norm)==1 &&
//                  EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
//         if (ctx) EVP_MD_CTX_free(ctx);
//         char lhex_now[65]={0};
//         if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));
//         free(line);

//         if (!ok || strcmp(lhex_now, tail.lhash) != 0) {
//             log_msg("preflight: last processed line content mismatch (idx lhash=%s, now=%s)",
//                     tail.lhash, lhex_now[0]?lhex_now:"<err>");
//             goto mismatch_and_rollback;
//         }
//         /* 前置校验通过 */
//     }

//     /* 正常尾读：从 g_offset 开始把 backlog 吃干净（用持久 FD） */
//     if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
//         log_msg("process_all_available_lines: fseeko failed: %s", strerror(errno));
//         return -1;
//     }
//     int consumed = 0;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, g_log_fp)) > 0) {
//         if (gmseal_on_line_from_logbuf(line, (size_t)n) != 0) {
//             log_msg("process_all_available_lines: on_line failed");
//             free(line); return -1;
//         }
//         consumed++;
//     }
//     free(line);
//     return consumed; // 0 表示当前没有新行

// mismatch_and_rollback: ;
//     /* 运行期发现 idx 尾项与当前日志不一致：回退到可信前缀并进入降级模式 */
//     trigger_alarm("Chain mismatch detected at runtime; rolling back to trusted prefix and entering degraded mode");
//     unsigned long long trusted_lines = 0, trusted_off = 0;
//     unsigned char trusted_chain[32]; memset(trusted_chain,0,sizeof(trusted_chain));
//     {
//         /* 用当前 LOG_PATH 计算可信前缀（若此刻正位于轮转中的旧 FD，也不会受新文件影响） */
//         int ret = find_trusted_prefix(LOG_PATH, IDX_PATH, &trusted_lines, trusted_chain, &trusted_off);
//         if (ret == 1) {
//             if (truncate_idx_to_i_atomic(IDX_PATH, trusted_lines) != 0) {
//                 log_msg("rollback: truncate idx to %llu failed", (unsigned long long)trusted_lines);
//             }
//             memcpy(g_chain, trusted_chain, 32);
//             g_lines = trusted_lines;
//             g_offset = trusted_off;
//             log_msg("rollback: trusted prefix lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         } else {
//             /* 一个可信前缀都找不到，全部清零 */
//             if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
//                 log_msg("rollback: truncate idx to 0 failed");
//             }
//             memset(g_chain,0,sizeof(g_chain));
//             g_lines = 0;
//             g_offset = 0;
//             log_msg("rollback: no trusted prefix; reset to 0");
//         }
//     }
//     g_degraded_mode = 1;  /* 降级：后续不写 seal，只报警 */
//     return 0;             /* 返回 0：调用方可继续循环，但不会产生新的 seal */
// }

// /* 初始化 */
// static int gmseal_init(void)
// {
//     memset(g_chain,0,sizeof(g_chain));
//     g_lines = 0; g_offset = 0; g_degraded_mode = 0;

//     if (open_idx_handle() != 0) log_msg("warning: cannot open idx on init; will attempt later");

//     /* 先打开持久 log 句柄，便于后续轮转检测 */
//     if (open_log_handle() != 0) {
//         log_msg("open_log_handle failed on init (may be recreated later)");
//     }

//     int vs = verify_existing_seal();
//     if (vs < 0) { log_msg("Existing seal mismatch/verify error - aborting"); return -1; }

//     unsigned long long log_lines = count_file_lines_simple(LOG_PATH);

//     if (vs > 0) { // 首次无 seal 或已“归位自愈”，当前视作新文件首轮
//         unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//         if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//             log_msg("rebuild_idx_from_log_atomic failed (first run)"); return -1;
//         }
//         if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//             log_msg("recover_tail_from_idx failed after first-run rebuild"); return -1;
//         }
//         if (reconcile_idx_with_log() != 0) return -1;
//         log_msg("first-run: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//     } else if(!g_degraded_mode){
//         if (log_lines > g_lines) {
//             log_msg("log has %llu > seal.records %llu -> resync", (unsigned long long)log_lines, (unsigned long long)g_lines);
//             unsigned char dummy[32]; unsigned long long nl=0, noff=0;
//             if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, dummy, &nl, &noff) != 0) {
//                 log_msg("rebuild_idx_from_log_atomic failed (resync)"); return -1;
//             }
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed after resync"); return -1;
//             }
//             if (reconcile_idx_with_log() != 0) return -1;
//             log_msg("resync: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         } else if (log_lines < g_lines) {
//             log_msg("log truncated: %llu < %llu, entering degraded & truncate idx", (unsigned long long)log_lines, (unsigned long long)g_lines);
//             g_degraded_mode = 1;
//             if (truncate_idx_to_i_atomic(IDX_PATH, log_lines) != 0) log_msg("truncate idx to %llu failed", (unsigned long long)log_lines);
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed after truncate"); return -1;
//             }
//             log_msg("truncate sync: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         } else {
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed (in sync)"); return -1;
//             }
//             log_msg("verified & in sync: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         }
//     } else {
//         log_msg("degraded mode after verify: skip resync/rebuild; holding at trusted prefix");
//     }

//     /* 初始化阶段做一次轮转检测（如果 auditd 已经提前轮转过） */
//     (void)check_rotation_and_rollover();
//     return 0;
// }

// /* stdin 驱动：每次触发都把 backlog 吃干净，并（可选）写 seal */
// static int stdin_event_loop(void)
// {
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (g_run && (n = getline(&line, &cap, stdin)) != -1) {
//         (void)n; // stdin 内容不使用，仅作触发

//         /* 先检测是否轮转 */
//         (void)check_rotation_and_rollover();

//         int rc = process_all_available_lines();
//         if (rc < 0) {
//             trigger_alarm("gmseal plugin processing error - exiting");
//             free(line); return -1;
//         }
//         if (ALWAYS_REFRESH_SEAL && rc > 0) {
//             if (gmseal_write_seal() != 0) log_msg("gmseal_write_seal failed");
//         }
//     }
//     if (line) free(line);
//     return 0;
// }

// /* main */
// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (acquire_singleton_lock() != 0) {
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 0; /* 非致命：另一实例在跑，我们退出 */
//     }

//     if (gm_crypto_init() != 0) {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         release_singleton_lock();
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0) {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         release_singleton_lock();
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 2;
//     }

//     /* 启动后先把当前可读尾部吃完，再写首次 seal，避免窗口不一致 */
//     {
//         int ate = process_all_available_lines();
//         if (ate < 0) {
//             log_msg("initial tail scan failed");
//         } else {
//             if (reconcile_idx_with_log() != 0) {
//                 log_msg("reconcile after initial scan failed");
//             }
//         }
//     }
//     /* 首次 seal 也走硬一致门闸 */
//     if (gmseal_write_seal() != 0) {
//         log_msg("initial seal write failed");
//     }

//     /* 进入主循环 */
//     if (stdin_event_loop() != 0) {
//         log_msg("stdin_event_loop aborted due to errors");
//     }

//     /* 结束前 seal（仍然先硬对齐），并再检测一次轮转做归档 */
//     (void)gmseal_write_seal();
//     (void)check_rotation_and_rollover();

//     close_idx_handle();
//     close_log_handle();
//     gm_crypto_cleanup();
//     release_singleton_lock();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }


//版本---解决篡改后出发审计覆盖导致审计不出来篡改行问题
// gmseal-plugin.c (append-only, no rebuild-after-tamper)
// *** COMPLETE FILE ***

// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>
// #include <sys/time.h>
// #include <dirent.h>
// #include <sys/sysmacros.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可改成周期） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// /* singleton lock */
// #define RUN_LOCK "/var/run/audisp-gmseal.lock"

// static FILE *g_log = NULL;
// static int g_run_lock_fd = -1;

// static void log_open(void)
// {
//    mkdir(GMLOG_DIR, 0755);
//    g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//    if (!g_log)
//       return;
//    va_list ap;
//    va_start(ap, fmt);
//    time_t t = time(NULL);
//    char tb[64];
//    strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//    fprintf(g_log, "[%s] ", tb);
//    vfprintf(g_log, fmt, ap);
//    fprintf(g_log, "\n");
//    fflush(g_log);
//    va_end(ap);
// }
// static int acquire_singleton_lock(void)
// {
//    g_run_lock_fd = open(RUN_LOCK, O_CREAT | O_RDWR, 0644);
//    if (g_run_lock_fd < 0)
//    {
//       log_msg("open run lock %s failed: %s", RUN_LOCK, strerror(errno));
//       return -1;
//    }
//    if (flock(g_run_lock_fd, LOCK_EX | LOCK_NB) != 0)
//    {
//       log_msg("another gmseal instance is running (lock busy), exiting");
//       return -1;
//    }
//    dprintf(g_run_lock_fd, "%ld\n", (long)getpid());
//    return 0;
// }
// static void release_singleton_lock(void)
// {
//    if (g_run_lock_fd >= 0)
//    {
//       flock(g_run_lock_fd, LOCK_UN);
//       close(g_run_lock_fd);
//       unlink(RUN_LOCK); /* 容忍失败 */
//       g_run_lock_fd = -1;
//    }
// }

// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//    static const char hex[] = "0123456789abcdef";
//    if (outlen < len * 2 + 1)
//       return;
//    for (size_t i = 0; i < len; i++)
//    {
//       out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//       out[i * 2 + 1] = hex[buf[i] & 0xF];
//    }
//    out[len * 2] = 0;
// }

// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//    BIO *b64 = BIO_new(BIO_f_base64());
//    BIO *mem = BIO_new(BIO_s_mem());
//    if (!b64 || !mem)
//    {
//       if (b64)
//          BIO_free(b64);
//       if (mem)
//          BIO_free(mem);
//       return NULL;
//    }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//    BIO_push(b64, mem);
//    if (BIO_write(b64, buf, (int)len) <= 0)
//    {
//       BIO_free_all(b64);
//       return NULL;
//    }
//    (void)BIO_flush(b64);
//    BUF_MEM *bptr = NULL;
//    BIO_get_mem_ptr(b64, &bptr);
//    char *out = malloc(bptr->length + 1);
//    if (!out)
//    {
//       BIO_free_all(b64);
//       return NULL;
//    }
//    memcpy(out, bptr->data, bptr->length);
//    out[bptr->length] = 0;
//    BIO_free_all(b64);
//    return out;
// }

// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//    BIO *b = BIO_new(BIO_f_base64());
//    BIO *m = BIO_new_mem_buf(b64, -1);
//    if (!b || !m)
//    {
//       if (b)
//          BIO_free(b);
//       if (m)
//          BIO_free(m);
//       return NULL;
//    }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//    BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//    BIO_push(b, m);
//    size_t cap = strlen(b64) * 3 / 4 + 16;
//    unsigned char *buf = malloc(cap);
//    int n = BIO_read(b, buf, (int)cap);
//    BIO_free_all(b);
//    if (n <= 0)
//    {
//       free(buf);
//       return NULL;
//    }
//    *olen = (size_t)n;
//    return buf;
// }

// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;
//    FILE *f = fopen(path, "rb");
//    if (!f)
//       return -1;
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    if (!ctx)
//    {
//       fclose(f);
//       return -1;
//    }
//    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1)
//    {
//       EVP_MD_CTX_free(ctx);
//       fclose(f);
//       return -1;
//    }
//    unsigned char buf[4096];
//    size_t n;
//    while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
//    {
//       if (EVP_DigestUpdate(ctx, buf, n) != 1)
//       {
//          EVP_MD_CTX_free(ctx);
//          fclose(f);
//          return -1;
//       }
//    }
//    fclose(f);
//    unsigned char md[32];
//    unsigned int mdlen = 0;
//    if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32)
//    {
//       EVP_MD_CTX_free(ctx);
//       return -1;
//    }
//    EVP_MD_CTX_free(ctx);
//    hexdump(md, 32, out_hex, 65);
//    return 0;
// }

// static size_t normalize_line(char *buf, size_t len)
// {
//    while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
//       len--;
//    return len;
// }

// static unsigned long long count_file_lines_simple(const char *path)
// {
//    FILE *f = fopen(path, "rb");
//    if (!f)
//       return 0;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    unsigned long long ln = 0;
//    while ((n = getline(&line, &cap, f)) != -1)
//       ln++;
//    free(line);
//    fclose(f);
//    return ln;
// }

// /* ------------ idx helpers ------------ */
// typedef struct
// {
//    unsigned long long i;
//    unsigned long long off;
//    char lhash[65];
//    char chash[65];
// } IdxEntry;

// typedef struct
// {
//    IdxEntry *entries;
//    size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a)
// {
//    if (!a)
//       return;
//    free(a->entries);
//    a->entries = NULL;
//    a->count = 0;
// }

// static int load_idx_entries(const char *path, IdxArray *out)
// {
//    memset(out, 0, sizeof(*out));
//    FILE *f = fopen(path, "r");
//    if (!f)
//       return -1;
//    char buf[1024];
//    IdxEntry *arr = NULL;
//    size_t cnt = 0, capv = 0;
//    while (fgets(buf, sizeof(buf), f))
//    {
//       if (buf[0] == '#')
//          continue;
//       unsigned long long ii = 0, off = 0;
//       char lhex[128] = {0}, chex[128] = {0};
//       if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &ii, &off, lhex, chex) == 4)
//       {
//          if (cnt == capv)
//          {
//             size_t nc = capv ? capv * 2 : 256;
//             IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//             if (!p)
//             {
//                free(arr);
//                fclose(f);
//                return -1;
//             }
//             arr = p;
//             capv = nc;
//          }
//          arr[cnt].i = ii;
//          arr[cnt].off = off;
//          strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash) - 1);
//          strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash) - 1);
//          cnt++;
//       }
//    }
//    fclose(f);
//    out->entries = arr;
//    out->count = cnt;
//    return 0;
// }

// /* 找可信前缀（idx 与 log 不一致时）—— 保留，但我们不再自动截短使用它 */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                                unsigned long long *out_trusted_lines,
//                                unsigned char out_chain[32],
//                                unsigned long long *out_offset)
// {
//    IdxArray idx = {0};
//    if (load_idx_entries(idxpath, &idx) != 0)
//    {
//       if (out_trusted_lines)
//          *out_trusted_lines = 0;
//       if (out_chain)
//          memset(out_chain, 0, 32);
//       if (out_offset)
//          *out_offset = 0;
//       free_idx_array(&idx);
//       return 0;
//    }

//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//    {
//       free_idx_array(&idx);
//       return -1;
//    }

//    FILE *lf = fopen(logpath, "rb");
//    if (!lf)
//    {
//       free_idx_array(&idx);
//       return -1;
//    }
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    unsigned char H[32];
//    memset(H, 0, sizeof(H));
//    unsigned long long ln = 0;
//    size_t idx_pos = 0;
//    unsigned long long last_good_i = 0, last_good_off = 0;
//    unsigned char last_good_chain[32];
//    memset(last_good_chain, 0, sizeof(last_good_chain));

//    while ((n = getline(&line, &cap, lf)) != -1)
//    {
//       ln++;
//       size_t normlen = normalize_line(line, (size_t)n);
//       EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//       unsigned int mdlen = 0;
//       if (!ctx)
//       {
//          free(line);
//          fclose(lf);
//          free_idx_array(&idx);
//          return -1;
//       }
//       if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//           EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//           EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//           EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//       {
//          EVP_MD_CTX_free(ctx);
//          free(line);
//          fclose(lf);
//          free_idx_array(&idx);
//          return -1;
//       }
//       EVP_MD_CTX_free(ctx);

//       if (idx_pos < idx.count)
//       {
//          IdxEntry *e = &idx.entries[idx_pos];
//          if (e->i == ln)
//          {
//             char hhex[65];
//             hexdump(H, 32, hhex, sizeof(hhex));
//             if (strcmp(hhex, e->chash) == 0)
//             {
//                last_good_i = e->i;
//                last_good_off = e->off;
//                memcpy(last_good_chain, H, 32);
//                idx_pos++;
//                continue;
//             }
//             else
//                break;
//          }
//          else if (e->i < ln)
//          {
//             while (idx_pos < idx.count && idx.entries[idx_pos].i < ln)
//                idx_pos++;
//             if (idx_pos < idx.count && idx.entries[idx_pos].i == ln)
//             {
//                char hhex[65];
//                hexdump(H, 32, hhex, sizeof(hhex));
//                if (strcmp(hhex, idx.entries[idx_pos].chash) == 0)
//                {
//                   last_good_i = idx.entries[idx_pos].i;
//                   last_good_off = idx.entries[idx_pos].off;
//                   memcpy(last_good_chain, H, 32);
//                   idx_pos++;
//                   continue;
//                }
//                else
//                   break;
//             }
//             else
//                break;
//          }
//          else
//             break;
//       }
//       else
//          break;
//    }

//    free(line);
//    fclose(lf);
//    if (out_trusted_lines)
//       *out_trusted_lines = last_good_i;
//    if (out_offset)
//       *out_offset = last_good_off;
//    if (out_chain)
//       memcpy(out_chain, last_good_chain, 32);
//    free_idx_array(&idx);
//    if (last_good_i > 0)
//       return 1;
//    return 0;
// }

// /* -------- seal 解析 -------- */
// struct seal
// {
//    char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static void trimnl(char *s)
// {
//    size_t n = strlen(s);
//    while (n && (s[n - 1] == '\n' || s[n - 1] == '\r'))
//       s[--n] = 0;
// }
// static char *trimws(char *s)
// {
//    while (*s == ' ' || *s == '\t')
//       s++;
//    size_t n = strlen(s);
//    while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' || s[n - 1] == '\n'))
//       s[--n] = 0;
//    return s;
// }

// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//    memset(S, 0, sizeof(*S));
//    FILE *f = fopen(seal_path, "r");
//    if (!f)
//       return -1;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    while ((n = getline(&line, &cap, f)) != -1)
//    {
//       if (line[0] == '#' || line[0] == 0)
//          continue;
//       trimnl(line);
//       char *eq = strchr(line, '=');
//       if (!eq)
//          continue;
//       *eq = 0;
//       char *k = trimws(line);
//       char *v = trimws(eq + 1);
//       if (strcmp(k, "version") == 0)
//          S->version = strdup(v);
//       else if (strcmp(k, "hash") == 0)
//          S->hash = strdup(v);
//       else if (strcmp(k, "sign") == 0)
//          S->sign = strdup(v);
//       else if (strcmp(k, "sm2_id") == 0)
//          S->sm2_id = strdup(v);
//       else if (strcmp(k, "path") == 0)
//          S->path = strdup(v);
//       else if (strcmp(k, "records") == 0)
//          S->records = strdup(v);
//       else if (strcmp(k, "chain") == 0)
//          S->chain = strdup(v);
//       else if (strcmp(k, "key_fpr") == 0)
//          S->key_fpr = strdup(v);
//       else if (strcmp(k, "cert_der_base64") == 0)
//          S->cert_b64 = strdup(v);
//       else if (strcmp(k, "sig_base64") == 0)
//          S->sig_b64 = strdup(v);
//       else if (strcmp(k, "index_sm3") == 0)
//          S->index_sm3 = strdup(v);
//    }
//    free(line);
//    fclose(f);
//    if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//        S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
//       return 0;
//    return -1;
// }
// static void free_seal(struct seal *S)
// {
//    free(S->version);
//    free(S->hash);
//    free(S->sign);
//    free(S->sm2_id);
//    free(S->path);
//    free(S->records);
//    free(S->chain);
//    free(S->key_fpr);
//    free(S->index_sm3);
//    free(S->cert_b64);
//    free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//    char *buf = NULL;
//    if (asprintf(&buf,
//                 "version=%s\n"
//                 "hash=%s\n"
//                 "sign=%s\n"
//                 "sm2_id=%s\n"
//                 "path=%s\n"
//                 "records=%s\n"
//                 "chain=%s\n"
//                 "key_fpr=%s\n"
//                 "index_sm3=%s\n",
//                 S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0)
//       return NULL;
//    return buf;
// }

// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;
//    unsigned char H[32] = {0};
//    unsigned long long scanned = 0;
//    FILE *fp = fopen(path, "rb");
//    if (!fp)
//       return -1;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1)
//    {
//       size_t normlen = normalize_line(line, (size_t)n);
//       EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//       if (!ctx)
//       {
//          fclose(fp);
//          free(line);
//          return -1;
//       }
//       int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                 EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                 EVP_DigestUpdate(ctx, line, normlen) == 1);
//       unsigned int mdlen = 0;
//       if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32)
//       {
//          EVP_MD_CTX_free(ctx);
//          fclose(fp);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(ctx);
//       scanned++;
//    }
//    free(line);
//    fclose(fp);
//    memcpy(out32, H, 32);
//    return 0;
// }

// /* ---------- idx handle ---------- */
// static int g_idx_fd = -1;
// static FILE *g_idx = NULL;

// static void close_idx_handle(void)
// {
//    if (g_idx)
//    {
//       fclose(g_idx);
//       g_idx = NULL;
//    }
//    if (g_idx_fd >= 0)
//    {
//       close(g_idx_fd);
//       g_idx_fd = -1;
//    }
// }
// static int open_idx_handle(void)
// {
//    if (g_idx_fd >= 0)
//       return 0;
//    int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
//    if (fd < 0)
//    {
//       log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
//       return -1;
//    }
//    FILE *f = fdopen(dup(fd), "a+");
//    if (!f)
//    {
//       log_msg("open_idx_handle: fdopen failed: %s", strerror(errno));
//       close(fd);
//       return -1;
//    }
//    g_idx_fd = fd;
//    g_idx = f;
//    fchmod(g_idx_fd, 0600);
//    return 0;
// }

// static int g_degraded_mode = 0;

// static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex)
// {
//    if (g_degraded_mode)
//    { /* NEW: 降级态禁止写入 */
//       log_msg("append_idx_line_atomic: degraded mode, skip append");
//       return 0;
//    }
//    if (g_idx_fd < 0)
//    {
//       log_msg("append_idx_line_atomic: idx fd not open");
//       return -1;
//    }
//    if (flock(g_idx_fd, LOCK_EX) != 0)
//    {
//       log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
//    }
//    int ret = 0;
//    ssize_t w = dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n",
//                        (unsigned long long)i, (unsigned long long)off, lhex, chex);
//    if (w < 0)
//    {
//       log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno));
//       ret = -1;
//    }
//    else
//       fsync(g_idx_fd);
//    flock(g_idx_fd, LOCK_UN);
//    return ret;
// }

// /* *** 全量重建：仅允许首次部署且 idx 为空时使用 *** */
// static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath,
//                                        unsigned char out_chain[32],
//                                        unsigned long long *out_lines,
//                                        unsigned long long *out_offset)
// {
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;
//    FILE *lf = fopen(logpath, "rb");
//    if (!lf)
//       return -1;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    unsigned long long ln = 0, offset = 0;
//    unsigned char H[32];
//    memset(H, 0, sizeof(H));

//    close_idx_handle();

//    char tmppath[PATH_MAX];
//    snprintf(tmppath, sizeof(tmppath), "%s.tmp", idxpath);
//    FILE *tf = fopen(tmppath, "w");
//    if (!tf)
//    {
//       fclose(lf);
//       return -1;
//    }
//    chmod(tmppath, 0600);

//    while ((n = getline(&line, &cap, lf)) != -1)
//    {
//       ln++;
//       size_t normlen = normalize_line(line, (size_t)n);
//       EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//       unsigned char L[32];
//       unsigned int lm = 0;
//       if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL) != 1 || EVP_DigestUpdate(c1, line, normlen) != 1 || EVP_DigestFinal_ex(c1, L, &lm) != 1 || lm != 32)
//       {
//          if (c1)
//             EVP_MD_CTX_free(c1);
//          fclose(lf);
//          fclose(tf);
//          unlink(tmppath);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(c1);

//       EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//       unsigned char newH[32];
//       unsigned int hm = 0;
//       if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL) != 1 || EVP_DigestUpdate(c2, H, sizeof(H)) != 1 || EVP_DigestUpdate(c2, line, normlen) != 1 || EVP_DigestFinal_ex(c2, newH, &hm) != 1 || hm != 32)
//       {
//          if (c2)
//             EVP_MD_CTX_free(c2);
//          fclose(lf);
//          fclose(tf);
//          unlink(tmppath);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(c2);
//       memcpy(H, newH, 32);

//       char lhex[65], chex[65];
//       hexdump(L, 32, lhex, sizeof(lhex));
//       hexdump(H, 32, chex, sizeof(chex));
//       if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                   (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0)
//       {
//          fclose(lf);
//          fclose(tf);
//          unlink(tmppath);
//          free(line);
//          return -1;
//       }
//       offset += (unsigned long long)n;
//    }
//    free(line);
//    fclose(lf);
//    fflush(tf);
//    fsync(fileno(tf));
//    fclose(tf);
//    if (rename(tmppath, idxpath) != 0)
//    {
//       unlink(tmppath);
//       return -1;
//    }
//    chmod(idxpath, 0600);
//    if (open_idx_handle() != 0)
//    {
//       log_msg("rebuild: open_idx_handle failed after rename");
//       return -1;
//    }
//    if (out_chain)
//       memcpy(out_chain, H, 32);
//    if (out_lines)
//       *out_lines = ln;
//    if (out_offset)
//       *out_offset = offset;
//    return 0;
// }

// static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
// {
//    if (g_degraded_mode)
//    { /* NEW: 降级态不允许截断 */
//       log_msg("truncate_idx_to_i_atomic: degraded mode, skip truncation");
//       return 0;
//    }
//    close_idx_handle();
//    FILE *in = fopen(idxpath, "r");
//    if (!in)
//    {
//       if (keep_i == 0)
//       {
//          FILE *out = fopen(idxpath, "w");
//          if (!out)
//             return -1;
//          fclose(out);
//          chmod(idxpath, 0600);
//          if (open_idx_handle() != 0)
//             return -1;
//          return 0;
//       }
//       return -1;
//    }
//    char tmp[PATH_MAX];
//    snprintf(tmp, sizeof(tmp), "%s.trunc", idxpath);
//    FILE *out = fopen(tmp, "w");
//    if (!out)
//    {
//       fclose(in);
//       return -1;
//    }
//    char buf[2048];
//    while (fgets(buf, sizeof(buf), in))
//    {
//       if (buf[0] == '#')
//       {
//          if (fputs(buf, out) < 0)
//          {
//             fclose(in);
//             fclose(out);
//             unlink(tmp);
//             return -1;
//          }
//          continue;
//       }
//       unsigned long long i = 0, off = 0;
//       char lhex[256] = {0}, chex[256] = {0};
//       if (sscanf(buf, "i=%llu off=%llu lhash=%255s chash=%255s", &i, &off, lhex, chex) == 4)
//       {
//          if (i <= keep_i)
//          {
//             if (fprintf(out, "i=%llu off=%llu lhash=%s chash=%s\n", i, off, lhex, chex) < 0)
//             {
//                fclose(in);
//                fclose(out);
//                unlink(tmp);
//                return -1;
//             }
//          }
//          else
//             break;
//       }
//    }
//    fclose(in);
//    fflush(out);
//    fsync(fileno(out));
//    fclose(out);
//    if (rename(tmp, idxpath) != 0)
//    {
//       unlink(tmp);
//       return -1;
//    }
//    chmod(idxpath, 0600);
//    if (open_idx_handle() != 0)
//    {
//       log_msg("truncate: open_idx_handle failed after rename");
//       return -1;
//    }
//    return 0;
// }

// /* ---------- global state ---------- */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static int g_run = 1;

// /* 持久持有的 audit.log 句柄与 inode，用于轮转检测 */
// static int g_log_fd = -1;
// static FILE *g_log_fp = NULL;
// static dev_t g_log_dev = 0;
// static ino_t g_log_ino = 0;

// static void on_signal(int sig)
// {
//    if (sig == SIGINT || sig == SIGTERM)
//       g_run = 0;
// }

// /* NEW: 统一进入降级模式的函数（只读、报警、保留现状） */
// static void enter_degraded_mode(const char *why)
// {
//    if (!g_degraded_mode)
//    {
//       g_degraded_mode = 1;
//       log_msg("ENTER DEGRADED MODE: %s", why ? why : "unknown");
//       // trigger_alarm("gmseal degraded mode: %s", why ? why : "unknown");
//    }
// }

// /* NEW: 打开/关闭当前 log 的句柄（持久） */
// static int open_log_handle(void)
// {
//    if (g_log_fd >= 0)
//       return 0;
//    g_log_fd = open(LOG_PATH, O_RDONLY | O_CLOEXEC);
//    if (g_log_fd < 0)
//    {
//       log_msg("open_log_handle: open %s failed: %s", LOG_PATH, strerror(errno));
//       return -1;
//    }
//    struct stat st;
//    if (fstat(g_log_fd, &st) != 0)
//    {
//       log_msg("open_log_handle: fstat failed: %s", strerror(errno));
//       close(g_log_fd);
//       g_log_fd = -1;
//       return -1;
//    }
//    g_log_dev = st.st_dev;
//    g_log_ino = st.st_ino;
//    g_log_fp = fdopen(dup(g_log_fd), "rb");
//    if (!g_log_fp)
//    {
//       log_msg("open_log_handle: fdopen failed: %s", strerror(errno));
//       close(g_log_fd);
//       g_log_fd = -1;
//       return -1;
//    }
//    return 0;
// }
// static void close_log_handle(void)
// {
//    if (g_log_fp)
//    {
//       fclose(g_log_fp);
//       g_log_fp = NULL;
//    }
//    if (g_log_fd >= 0)
//    {
//       close(g_log_fd);
//       g_log_fd = -1;
//    }
//    g_log_dev = 0;
//    g_log_ino = 0;
// }

// /* NEW: 从 idx 尾恢复 */
// static int recover_tail_from_idx(unsigned long long *out_i,
//                                  unsigned long long *out_off,
//                                  unsigned char out_chain[32])
// {
//    FILE *ix = fopen(IDX_PATH, "r");
//    if (!ix)
//       return -1;
//    char buf[1024];
//    unsigned long long last_i = 0, last_off = 0;
//    char last_ch[65] = {0};
//    while (fgets(buf, sizeof(buf), ix))
//    {
//       if (buf[0] == '#')
//          continue;
//       unsigned long long i = 0, off = 0;
//       char l[128] = {0}, c[128] = {0};
//       if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, l, c) == 4)
//       {
//          last_i = i;
//          last_off = off;
//          strncpy(last_ch, c, sizeof(last_ch) - 1);
//       }
//    }
//    fclose(ix);
//    if (out_i)
//       *out_i = last_i;
//    if (out_off)
//       *out_off = last_off;
//    if (out_chain)
//    {
//       if (last_i == 0)
//       {
//          memset(out_chain, 0, 32);
//          return 0;
//       }
//       for (int i = 0; i < 32; i++)
//       {
//          unsigned int hi, lo;
//          if (sscanf(&last_ch[i * 2], "%1x%1x", &hi, &lo) != 2)
//             return -1;
//          out_chain[i] = (hi << 4) | lo;
//       }
//    }
//    return 0;
// }

// /* NEW: 读取 idx 最后一条的完整 4 元组 */
// static int read_last_idx_entry(IdxEntry *out)
// {
//    if (!out)
//       return -1;
//    memset(out, 0, sizeof(*out));
//    FILE *ix = fopen(IDX_PATH, "r");
//    if (!ix)
//       return -1;
//    char buf[1024];
//    int have = 0;
//    while (fgets(buf, sizeof(buf), ix))
//    {
//       if (buf[0] == '#')
//          continue;
//       unsigned long long i = 0, off = 0;
//       char lhex[128] = {0}, chex[128] = {0};
//       if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//       {
//          out->i = i;
//          out->off = off;
//          strncpy(out->lhash, lhex, sizeof(out->lhash) - 1);
//          strncpy(out->chash, chex, sizeof(out->chash) - 1);
//          have = 1;
//       }
//    }
//    fclose(ix);
//    return have ? 0 : -1;
// }

// /* NEW: 仅用于统计 idx 行数（判断是否空 idx） */
// static unsigned long long idx_count_lines(void)
// {
//    FILE *f = fopen(IDX_PATH, "r");
//    if (!f)
//       return 0;
//    char buf[256];
//    unsigned long long cnt = 0;
//    while (fgets(buf, sizeof(buf), f))
//    {
//       if (buf[0] == '#')
//          continue;
//       unsigned long long i = 0, off = 0;
//       char l[128], c[128];
//       if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, l, c) == 4)
//          cnt++;
//    }
//    fclose(f);
//    return cnt;
// }

// /* NEW: 旧文件 drain 到 EOF */
// static int drain_old_file_to_eof(void)
// {
//    if (!g_log_fp)
//       return 0;
//    if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0)
//    {
//       log_msg("drain_old_file_to_eof: fseeko failed: %s", strerror(errno));
//       return -1;
//    }
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    int cnt = 0;
//    while ((n = getline(&line, &cap, g_log_fp)) > 0)
//    {
//       const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//       if (!sm3)
//       {
//          free(line);
//          return -1;
//       }
//       size_t normlen = normalize_line(line, (size_t)n);

//       unsigned char lhash[32], cand_chain[32];
//       unsigned int lm = 0, hm = 0;

//       EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//       if (!c1)
//       {
//          free(line);
//          return -1;
//       }
//       if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//           EVP_DigestUpdate(c1, line, normlen) != 1 ||
//           EVP_DigestFinal_ex(c1, lhash, &lm) != 1 || lm != 32)
//       {
//          EVP_MD_CTX_free(c1);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(c1);

//       EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//       if (!c2)
//       {
//          free(line);
//          return -1;
//       }
//       if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//           EVP_DigestUpdate(c2, g_chain, sizeof(g_chain)) != 1 ||
//           EVP_DigestUpdate(c2, line, normlen) != 1 ||
//           EVP_DigestFinal_ex(c2, cand_chain, &hm) != 1 || hm != 32)
//       {
//          EVP_MD_CTX_free(c2);
//          free(line);
//          return -1;
//       }
//       EVP_MD_CTX_free(c2);

//       char lhex[65], chex[65];
//       hexdump(lhash, 32, lhex, sizeof(lhex));
//       hexdump(cand_chain, 32, chex, sizeof(chex));

//       if (g_idx_fd < 0 && open_idx_handle() != 0)
//       {
//          log_msg("drain_old_file_to_eof: open_idx_handle failed");
//          free(line);
//          return -1;
//       }
//       if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0)
//       {
//          log_msg("drain_old_file_to_eof: append idx failed");
//          free(line);
//          return -1;
//       }
//       memcpy(g_chain, cand_chain, 32);
//       g_lines++;
//       g_offset += (unsigned long long)n;
//       cnt++;
//    }
//    free(line);
//    return cnt;
// }

// /* ---- 写 seal 的通用函数：为指定 log/idx 输出到指定 seal 文件 ---- */
// static int gmseal_write_seal_for(const char *log_path_for_canon,
//                                  const char *idx_path_for_hash,
//                                  const char *seal_out_path)
// {
//    if (g_degraded_mode)
//    {
//       log_msg("In degraded mode: skip writing seal_for(%s)", log_path_for_canon);
//       return 0;
//    }

//    EVP_PKEY *pkey = NULL;
//    X509 *cert = NULL;

//    FILE *pf = fopen(PASS_PATH, "r");
//    if (!pf)
//    {
//       log_msg("open PASS_PATH failed");
//       return -1;
//    }
//    char *secret = NULL;
//    size_t cap = 0;
//    ssize_t n = getline(&secret, &cap, pf);
//    fclose(pf);
//    if (n <= 0)
//    {
//       free(secret);
//       log_msg("read PASS_PATH empty");
//       return -1;
//    }
//    while (n && (secret[n - 1] == '\n' || secret[n - 1] == '\r'))
//       secret[--n] = 0;

//    unsigned char salt[SALT_BYTES];
//    FILE *sf = fopen(SALT_PATH, "r");
//    if (!sf)
//    {
//       log_msg("open SALT_PATH failed");
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    char saltbuf[256];
//    if (!fgets(saltbuf, sizeof(saltbuf), sf))
//    {
//       fclose(sf);
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    fclose(sf);
//    size_t slen = strlen(saltbuf);
//    while (slen && (saltbuf[slen - 1] == '\n' || saltbuf[slen - 1] == '\r'))
//       saltbuf[--slen] = 0;
//    if (slen != (size_t)SALT_BYTES * 2)
//    {
//       log_msg("salt length error");
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    for (size_t i = 0; i < SALT_BYTES; i++)
//    {
//       unsigned int hi, lo;
//       if (sscanf(&saltbuf[i * 2], "%1x%1x", &hi, &lo) != 2)
//       {
//          OPENSSL_cleanse(secret, strlen(secret));
//          free(secret);
//          return -1;
//       }
//       salt[i] = (hi << 4) | lo;
//    }

//    unsigned char derived[DERIVE_BYTES];
//    if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived))
//    {
//       log_msg("PBKDF2-SM3 failed");
//       OPENSSL_cleanse(secret, strlen(secret));
//       free(secret);
//       return -1;
//    }
//    char passhex[DERIVE_BYTES * 2 + 1];
//    hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//    OPENSSL_cleanse(secret, strlen(secret));
//    free(secret);

//    FILE *pf2 = fopen(P12_PATH, "rb");
//    if (!pf2)
//    {
//       log_msg("open P12_PATH failed");
//       return -1;
//    }
//    PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL);
//    fclose(pf2);
//    if (!p12)
//    {
//       log_msg("d2i_PKCS12_fp failed");
//       return -1;
//    }
//    if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL))
//    {
//       log_msg("PKCS12_parse failed");
//       PKCS12_free(p12);
//       return -1;
//    }
//    PKCS12_free(p12);

//    unsigned char *der = NULL;
//    int derlen = i2d_X509(cert, &der);
//    if (derlen <= 0)
//    {
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    unsigned char mdv[EVP_MAX_MD_SIZE];
//    unsigned int mdlen = 0;
//    if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//    {
//       if (ctx)
//          EVP_MD_CTX_free(ctx);
//       OPENSSL_free(der);
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }
//    EVP_MD_CTX_free(ctx);
//    OPENSSL_free(der);
//    char fpr_hex[65];
//    hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//    char chain_hex[65];
//    hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//    char idx_sm3[65] = {0};
//    if (sm3_file_hex(idx_path_for_hash, idx_sm3) != 0)
//    {
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       log_msg("sm3 idx failed for %s", idx_path_for_hash);
//       return -1;
//    }

//    char *canon = NULL;
//    if (asprintf(&canon, "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
//                 SM2_ID, log_path_for_canon, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0)
//    {
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }

//    unsigned char *sig = NULL;
//    size_t siglen = 0;
//    if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0)
//    {
//       log_msg("gm_sm2_sign failed");
//       free(canon);
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }
//    char *sigb64 = b64_encode(sig, siglen);
//    OPENSSL_free(sig);

//    unsigned char *der2 = NULL;
//    int der2len = i2d_X509(cert, &der2);
//    char *certb64 = NULL;
//    if (der2len > 0)
//    {
//       certb64 = b64_encode(der2, der2len);
//       OPENSSL_free(der2);
//    }
//    else
//    {
//       log_msg("i2d_X509 failed");
//       free(canon);
//       free(sigb64);
//       EVP_PKEY_free(pkey);
//       X509_free(cert);
//       return -1;
//    }

//    EVP_PKEY_free(pkey);
//    X509_free(cert);

//    char tmp[PATH_MAX];
//    snprintf(tmp, sizeof(tmp), "%s.tmp", seal_out_path);
//    FILE *sfw = fopen(tmp, "w");
//    if (!sfw)
//    {
//       log_msg("open seal tmp failed: %s", strerror(errno));
//       free(canon);
//       free(sigb64);
//       free(certb64);
//       return -1;
//    }
//    fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//    fprintf(sfw, "cert_der_base64=%s\n", certb64);
//    fprintf(sfw, "sig_base64=%s\n", sigb64);
//    fclose(sfw);
//    chmod(tmp, 0600);
//    if (rename(tmp, seal_out_path) != 0)
//    {
//       log_msg("rename seal tmp -> %s failed: %s", seal_out_path, strerror(errno));
//       unlink(tmp);
//       free(canon);
//       free(sigb64);
//       free(certb64);
//       return -1;
//    }
//    free(canon);
//    free(sigb64);
//    free(certb64);

//    log_msg("seal written: %s (records=%llu path=%s)", seal_out_path, (unsigned long long)g_lines, log_path_for_canon);
//    return 0;
// }

// /* NEW: 把当前 .idx/.seal 归档到已轮转的旧文件名 */
// static int archive_current_idx_and_seal_to(const char *archived_log_path)
// {
//    char idx_dst[PATH_MAX], seal_dst[PATH_MAX];
//    snprintf(idx_dst, sizeof(idx_dst), "%s.idx", archived_log_path);
//    snprintf(seal_dst, sizeof(seal_dst), "%s.seal", archived_log_path);

//    close_idx_handle();

//    if (rename(IDX_PATH, idx_dst) != 0)
//    {
//       log_msg("archive: rename %s -> %s failed: %s", IDX_PATH, idx_dst, strerror(errno));
//       return -1;
//    }
//    chmod(idx_dst, 0600);

//    if (gmseal_write_seal_for(archived_log_path, idx_dst, seal_dst) != 0)
//    {
//       log_msg("archive: write seal_for(%s) failed", archived_log_path);
//       return -1;
//    }
//    return 0;
// }

// /* NEW: 用 inode 在 /var/log/audit 查找已轮转的旧文件名 */
// static int find_rotated_name_by_inode(char out_path[PATH_MAX], dev_t dev, ino_t ino)
// {
//    DIR *d = opendir("/var/log/audit");
//    if (!d)
//       return -1;
//    struct dirent *de;
//    struct stat st;
//    while ((de = readdir(d)))
//    {
//       if (strncmp(de->d_name, "audit.log", 9) != 0)
//          continue;
//       char cand[PATH_MAX];
//       snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
//       if (stat(cand, &st) != 0)
//          continue;
//       if (st.st_dev == dev && st.st_ino == ino)
//       {
//          strncpy(out_path, cand, PATH_MAX - 1);
//          out_path[PATH_MAX - 1] = 0;
//          closedir(d);
//          return 0;
//       }
//    }
//    closedir(d);
//    return -1;
// }

// /* NEW: 检测轮转并执行归档与切换（严格区分“合法轮转”和“可疑重写”） */
// static int check_rotation_and_rollover(void)
// {
//    if (g_log_fd < 0)
//       return open_log_handle();

//    struct stat st_now;
//    if (stat(LOG_PATH, &st_now) != 0)
//    {
//       return 0;
//    }
//    if (st_now.st_dev == g_log_dev && st_now.st_ino == g_log_ino)
//       return 0;

//    /* inode 变了：先把旧 FD 指向的旧内容彻底 drain 完成索引 */
//    log_msg("detected inode change: old ino=%ju -> new ino=%ju",
//            (uintmax_t)g_log_ino, (uintmax_t)st_now.st_ino);

//    (void)drain_old_file_to_eof();

//    /* 判断是否真轮转：能否找到旧 inode 对应的归档文件名 */
//    char archived_log[PATH_MAX] = {0};
//    if (find_rotated_name_by_inode(archived_log, g_log_dev, g_log_ino) == 0)
//    {
//       /* 合法轮转 —— 归档 idx/seal 到旧名，再切换新代 */
//       log_msg("rotation confirmed: archived file = %s", archived_log);
//       (void)archive_current_idx_and_seal_to(archived_log);

//       close_log_handle();
//       if (open_log_handle() != 0)
//       {
//          log_msg("rotation: open new audit.log failed");
//          return -1;
//       }

//       /* 新代从 0 开始（append-only） */
//       if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0)
//       {
//          log_msg("rotation: truncate new idx failed");
//          return -1;
//       }
//       memset(g_chain, 0, sizeof(g_chain));
//       g_lines = 0;
//       g_offset = 0;

//       g_degraded_mode = 0; // NEW: 真轮转可清除降级

//       /* 可选：写一版“空链”seal */
//       (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
//       return 0;
//    }

//    /* 找不到旧文件名：不是合法轮转，多半是 sed -i 之类的可疑重写 */
//    log_msg("inode changed but no archived filename found -> suspicious rewrite; keep idx/seal, enter degraded mode");
//    enter_degraded_mode("audit.log suspicious rewrite (inode changed without rotated file)");
//    /* 仍然切到新文件句柄，但降级态下不再写入 idx/seal */
//    close_log_handle();
//    (void)open_log_handle();
//    /* 注意：不修改 g_chain/g_lines/g_offset，不截断 idx，不写 seal */
//    return 0;
// }

// /* 验证现有 seal；若是上一代的封印则归位；若任何不一致且无法归位 => 进入降级模式但不截断 idx */
// static int try_rehome_stale_seal_to_rotated_file(void)
// {
//    if (access(SEAL_PATH, R_OK) != 0)
//       return -1;
//    struct seal S;
//    if (parse_seal_file(SEAL_PATH, &S) != 0)
//       return -1;
//    unsigned long long N = strtoull(S.records ? S.records : "0", NULL, 10);
//    if (N == 0 || !S.chain)
//    {
//       free_seal(&S);
//       return -1;
//    }

//    DIR *d = opendir("/var/log/audit");
//    if (!d)
//    {
//       free_seal(&S);
//       return -1;
//    }
//    struct dirent *de;
//    int ok = -1;
//    while ((de = readdir(d)))
//    {
//       if (strncmp(de->d_name, "audit.log.", 10) != 0)
//          continue;
//       char cand[PATH_MAX];
//       snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
//       unsigned char chainR[32];
//       if (sm3_chain_file_limit_local(cand, N, chainR) != 0)
//          continue;
//       char chx[65];
//       hexdump(chainR, 32, chx, sizeof(chx));
//       if (strcmp(chx, S.chain) == 0)
//       {
//          (void)archive_current_idx_and_seal_to(cand);
//          ok = 0;
//          break;
//       }
//    }
//    closedir(d);
//    free_seal(&S);
//    if (ok == 0)
//    {
//       (void)truncate_idx_to_i_atomic(IDX_PATH, 0);
//       memset(g_chain, 0, sizeof(g_chain));
//       g_lines = 0;
//       g_offset = 0;
//       g_degraded_mode = 0;
//       (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
//    }
//    return ok;
// }

// /* 验证现有 seal：任何一致性失败 -> 能归位就归位；否则仅进入降级，不截断/重建 */
// static int verify_existing_seal(void)
// {
//    if (access(SEAL_PATH, R_OK) != 0)
//    {
//       log_msg("no existing seal: treat as first-run");
//       return 1; /* 首次无 seal */
//    }

//    struct seal S;
//    if (parse_seal_file(SEAL_PATH, &S) != 0)
//    {
//       log_msg("parse existing seal failed");
//       trigger_alarm("Existing seal is not parseable - manual recovery needed");
//       /* CHG: 不删除 idx，不当作 first-run；进入降级 */
//       enter_degraded_mode("existing seal parse failed");
//       free_seal(&S);
//       return 0;
//    }
//    if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0 || strcmp(S.sm2_id, SM2_ID) != 0)
//    {
//       log_msg("existing seal algorithm mismatch");
//       trigger_alarm("Existing seal algorithm mismatch");
//       enter_degraded_mode("existing seal algorithm mismatch");
//       free_seal(&S);
//       return 0;
//    }

//    size_t cert_len = 0;
//    unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//    if (!cert_der)
//    {
//       free_seal(&S);
//       trigger_alarm("Existing seal cert b64 decode failed");
//       enter_degraded_mode("seal cert b64 decode failed");
//       return 0;
//    }
//    const unsigned char *p = cert_der;
//    X509 *cert = d2i_X509(NULL, &p, cert_len);
//    free(cert_der);
//    if (!cert)
//    {
//       free_seal(&S);
//       trigger_alarm("Existing seal d2i_X509 failed");
//       enter_degraded_mode("seal d2i_X509 failed");
//       return 0;
//    }
//    EVP_PKEY *pub = X509_get_pubkey(cert);
//    if (!pub)
//    {
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal get pubkey failed");
//       enter_degraded_mode("seal get pubkey failed");
//       return 0;
//    }
//    EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//    if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal cert is not SM2");
//       enter_degraded_mode("seal cert not SM2");
//       return 0;
//    }

//    unsigned char *der2 = NULL;
//    int der2len = i2d_X509(cert, &der2);
//    if (der2len <= 0)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal i2d_X509 failed");
//       enter_degraded_mode("seal i2d_X509 failed");
//       return 0;
//    }
//    unsigned char mdv[EVP_MAX_MD_SIZE];
//    unsigned int mdlen = 0;
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
//        EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//        EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1)
//    {
//       if (ctx)
//          EVP_MD_CTX_free(ctx);
//       OPENSSL_free(der2);
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal fingerprint digest failed");
//       enter_degraded_mode("seal fingerprint digest failed");
//       return 0;
//    }
//    EVP_MD_CTX_free(ctx);
//    OPENSSL_free(der2);
//    char fpr_hex[65];
//    hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));
//    if (strcmp(fpr_hex, S.key_fpr) != 0)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal certificate fingerprint mismatch");
//       enter_degraded_mode("seal key_fpr mismatch");
//       return 0;
//    }

//    char *canon = build_canon_from_seal(&S);
//    if (!canon)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("asprintf canon failed");
//       enter_degraded_mode("seal canon build failed");
//       return 0;
//    }
//    size_t siglen = 0;
//    unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//    if (!sig)
//    {
//       free(canon);
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal sig b64 decode failed");
//       enter_degraded_mode("seal sig b64 decode failed");
//       return 0;
//    }
//    int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//    free(sig);
//    free(canon);
//    if (v != 1)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing seal signature invalid");
//       enter_degraded_mode("seal signature invalid");
//       return 0;
//    }

//    unsigned long long N = strtoull(S.records, NULL, 10);

//    if (N > 0)
//    {
//       unsigned char chainR[32];
//       if (sm3_chain_file_limit_local(S.path, N, chainR) != 0)
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Cannot compute chain from audit.log for existing seal");
//          enter_degraded_mode("cannot compute chain from audit.log");
//          return 0;
//       }
//       char chx[65];
//       hexdump(chainR, 32, chx, sizeof(chx));
//       if (strcmp(chx, S.chain) != 0)
//       {
//          log_msg("Existing seal chain mismatch; trying to rehome stale seal...");
//          if (try_rehome_stale_seal_to_rotated_file() == 0)
//          {
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             log_msg("stale seal rehomed to rotated file; treating as first-run for current log");
//             return 1;
//          }
//          /* CHG: 不做 trusted_prefix 截短，进入降级并保留 idx */
//          log_msg("Existing seal chain mismatch and cannot rehome -> enter degraded mode, keep idx/seal");
//          enter_degraded_mode("existing seal chain mismatch");
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          return 0;
//       }
//    }

//    if (access(IDX_PATH, R_OK) == 0)
//    {
//       char now_sm3[65];
//       if (sm3_file_hex(IDX_PATH, now_sm3) != 0)
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Cannot compute idx sm3");
//          enter_degraded_mode("cannot compute idx sm3");
//          return 0;
//       }
//       if (strcmp(now_sm3, S.index_sm3) != 0)
//       {
//          log_msg("Index file hash mismatch; trying to rehome stale seal...");
//          if (try_rehome_stale_seal_to_rotated_file() == 0)
//          {
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             log_msg("stale seal rehomed; treating as first-run");
//             return 1;
//          }
//          log_msg("Index file hash mismatch and cannot rehome -> enter degraded mode, keep idx/seal");
//          enter_degraded_mode("index file sm3 mismatch");
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          return 0;
//       }

//       FILE *ix = fopen(IDX_PATH, "r");
//       if (ix)
//       {
//          char buf[1024];
//          unsigned long long last_i = 0;
//          char last_ch[65] = {0};
//          while (fgets(buf, sizeof(buf), ix))
//          {
//             if (buf[0] == '#')
//                continue;
//             unsigned long long i = 0, off = 0;
//             char l[128] = {0}, c[128] = {0};
//             if (sscanf(buf, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, l, c) == 4)
//             {
//                last_i = i;
//                strncpy(last_ch, c, sizeof(last_ch) - 1);
//             }
//          }
//          fclose(ix);
//          if (last_i != N || strcmp(last_ch, S.chain) != 0)
//          {
//             log_msg("seal vs idx tail mismatch; trying to rehome stale seal...");
//             if (try_rehome_stale_seal_to_rotated_file() == 0)
//             {
//                EVP_PKEY_free(pub);
//                X509_free(cert);
//                free_seal(&S);
//                log_msg("stale seal rehomed; treating as first-run");
//                return 1;
//             }
//             log_msg("seal vs idx tail mismatch and cannot rehome -> enter degraded mode, keep idx/seal");
//             enter_degraded_mode("seal vs idx tail mismatch");
//             EVP_PKEY_free(pub);
//             X509_free(cert);
//             free_seal(&S);
//             return 0;
//          }
//       }
//       else
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Cannot open idx for existing seal");
//          enter_degraded_mode("cannot open idx");
//          return 0;
//       }
//    }
//    else
//    {
//       if (try_rehome_stale_seal_to_rotated_file() == 0)
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          log_msg("stale seal rehomed; treating as first-run");
//          return 1;
//       }
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("No idx file but existing seal expects one");
//       enter_degraded_mode("no idx but seal exists");
//       return 0;
//    }

//    /* 一切匹配：把 seal 的链和行数装回内存状态（用于继续处理） */
//    unsigned long long N2 = strtoull(S.records, NULL, 10);
//    for (int i = 0; i < 32; i++)
//    {
//       unsigned int hi, lo;
//       if (sscanf(&S.chain[i * 2], "%1x%1x", &hi, &lo) != 2)
//       {
//          EVP_PKEY_free(pub);
//          X509_free(cert);
//          free_seal(&S);
//          trigger_alarm("Existing seal chain hex parse error");
//          enter_degraded_mode("seal chain hex parse error");
//          return 0;
//       }
//       g_chain[i] = (hi << 4) | lo;
//    }
//    g_lines = N2;

//    FILE *ix2 = fopen(IDX_PATH, "r");
//    if (!ix2)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Cannot open idx for reading offset");
//       enter_degraded_mode("cannot open idx for offset");
//       return 0;
//    }
//    char b2[1024];
//    unsigned long long last_off = 0;
//    unsigned long long last_i = 0;
//    while (fgets(b2, sizeof(b2), ix2))
//    {
//       if (b2[0] == '#')
//          continue;
//       unsigned long long i = 0, off = 0;
//       char lhex[128] = {0}, chex[128] = {0};
//       if (sscanf(b2, "i=%llu off=%llu lhash=%127s chash=%127s", &i, &off, lhex, chex) == 4)
//       {
//          last_i = i;
//          last_off = off;
//       }
//    }
//    fclose(ix2);
//    if (last_i != g_lines)
//    {
//       EVP_PKEY_free(pub);
//       X509_free(cert);
//       free_seal(&S);
//       trigger_alarm("Existing idx last_i != seal records - inconsistent");
//       enter_degraded_mode("idx last_i != seal records");
//       return 0;
//    }
//    g_offset = last_off;

//    EVP_PKEY_free(pub);
//    X509_free(cert);
//    free_seal(&S);
//    return 0;
// }

// /* 处理一行（从 audit.log 读取的内容） */
// static int gmseal_on_line_from_logbuf(const char *buf, size_t raw_n)
// {
//    if (g_degraded_mode)
//       return 0; /* NEW: 降级态不写入 */

//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    if (!sm3)
//       return -1;

//    size_t normlen = raw_n;
//    while (normlen > 0 && (buf[normlen - 1] == '\n' || buf[normlen - 1] == '\r'))
//       normlen--;

//    unsigned char lhash[32], cand_chain[32];
//    unsigned int lm = 0, hm = 0;

//    EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//    if (!c1)
//       return -1;
//    if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//        EVP_DigestUpdate(c1, buf, normlen) != 1 ||
//        EVP_DigestFinal_ex(c1, lhash, &lm) != 1 || lm != 32)
//    {
//       EVP_MD_CTX_free(c1);
//       return -1;
//    }
//    EVP_MD_CTX_free(c1);

//    EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//    if (!c2)
//       return -1;
//    if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//        EVP_DigestUpdate(c2, g_chain, sizeof(g_chain)) != 1 ||
//        EVP_DigestUpdate(c2, buf, normlen) != 1 ||
//        EVP_DigestFinal_ex(c2, cand_chain, &hm) != 1 || hm != 32)
//    {
//       EVP_MD_CTX_free(c2);
//       return -1;
//    }
//    EVP_MD_CTX_free(c2);

//    char lhex[65], chex[65];
//    hexdump(lhash, 32, lhex, sizeof(lhex));
//    hexdump(cand_chain, 32, chex, sizeof(chex));

//    if (g_idx_fd < 0 && open_idx_handle() != 0)
//    {
//       log_msg("gmseal_on_line: open_idx_handle failed");
//       return -1;
//    }

//    /* FIX: 兜底的“offset 单调”检查，防止重复入库同一偏移的行 */
//    IdxEntry tail = {0};
//    if (read_last_idx_entry(&tail) == 0)
//    {
//       if (g_lines == tail.i && g_offset <= tail.off)
//       {
//          log_msg("gmseal_on_line: non-monotonic offset (g_offset=%llu <= tail.off=%llu), skip duplicate",
//                  (unsigned long long)g_offset, (unsigned long long)tail.off);
//          return 0;
//       }
//       if (tail.i == g_lines && strncmp(tail.chash, chex, sizeof(tail.chash)) == 0)
//       {
//          return 0;
//       }
//    }

//    if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0)
//    {
//       log_msg("gmseal_on_line: append_idx_line_atomic failed for next i=%llu", (unsigned long long)(g_lines + 1));
//       return -1;
//    }

//    memcpy(g_chain, cand_chain, 32);
//    g_lines++;
//    g_offset += (unsigned long long)raw_n;
//    return 0;
// }

// /* 读取 audit.log 的 tail 全部吃完（带运行期前置校验） */
// static int process_all_available_lines(void)
// {
//    if (g_degraded_mode)
//       return 0; /* NEW: 降级态下不做读写，保持证据链 */
//    if (!g_log_fp && open_log_handle() != 0)
//       return -1;

//    if (g_lines > 0)
//    {
//       IdxEntry tail = {0};
//       if (read_last_idx_entry(&tail) != 0)
//       {
//          log_msg("preflight: cannot read last idx entry");
//          enter_degraded_mode("Runtime preflight failed: cannot read last idx entry");
//          return -1;
//       }
//       if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0)
//       {
//          log_msg("preflight: fseeko to tail.off=%llu failed: %s",
//                  (unsigned long long)tail.off, strerror(errno));
//          enter_degraded_mode("preflight fseeko failed");
//          return 0;
//       }
//       char *line = NULL;
//       size_t cap = 0;
//       ssize_t n = getline(&line, &cap, g_log_fp);
//       if (n <= 0)
//       {
//          free(line);
//          log_msg("preflight: cannot read line at off=%llu", (unsigned long long)tail.off);
//          enter_degraded_mode("preflight cannot read last line");
//          return 0;
//       }
//       size_t norm = normalize_line(line, (size_t)n);
//       const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//       unsigned char md[32];
//       unsigned int mdlen = 0;
//       EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//       int ok = ctx && sm3 &&
//                EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                EVP_DigestUpdate(ctx, line, norm) == 1 &&
//                EVP_DigestFinal_ex(ctx, md, &mdlen) == 1 && mdlen == 32;
//       if (ctx)
//          EVP_MD_CTX_free(ctx);
//       char lhex_now[65] = {0};
//       if (ok)
//          hexdump(md, 32, lhex_now, sizeof(lhex_now));

//       if (!ok || strcmp(lhex_now, tail.lhash) != 0)
//       {
//          log_msg("preflight: last processed line content mismatch (idx lhash=%s, now=%s)",
//                  tail.lhash, lhex_now[0] ? lhex_now : "<err>");
//          free(line);
//          /* CHG: 不做回退截断，直接进入降级，保留原 idx 作为证据 */
//          enter_degraded_mode("preflight mismatch at runtime");
//          return 0;
//       }

//       /* FIX: 预检通过后，把 g_offset 前移到“最后一行的结尾”，避免重复入库 */
//       g_offset = tail.off + (unsigned long long)n;
//       free(line);
//    }

//    if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0)
//    {
//       log_msg("process_all_available_lines: fseeko failed: %s", strerror(errno));
//       return -1;
//    }
//    int consumed = 0;
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    while ((n = getline(&line, &cap, g_log_fp)) > 0)
//    {
//       if (gmseal_on_line_from_logbuf(line, (size_t)n) != 0)
//       {
//          log_msg("process_all_available_lines: on_line failed");
//          free(line);
//          return -1;
//       }
//       consumed++;
//    }
//    free(line);
//    return consumed;
// }

// /* NEW: 写 seal 前做一次“只读预检”，不做任何 rebuild/截短 */
// static int safe_pre_seal_check(void)
// {
//    if (g_degraded_mode)
//       return -1;
//    if (g_lines == 0)
//       return 0;
//    if (!g_log_fp && open_log_handle() != 0)
//       return -1;

//    IdxEntry tail = {0};
//    if (read_last_idx_entry(&tail) != 0)
//       return -1;

//    if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0)
//       return -1;

//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n = getline(&line, &cap, g_log_fp);
//    if (n <= 0)
//    {
//       free(line);
//       return -1;
//    }
//    size_t norm = normalize_line(line, (size_t)n);
//    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//    unsigned char md[32];
//    unsigned int mdlen = 0;
//    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//    int ok = ctx && sm3 &&
//             EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//             EVP_DigestUpdate(ctx, line, norm) == 1 &&
//             EVP_DigestFinal_ex(ctx, md, &mdlen) == 1 && mdlen == 32;
//    if (ctx)
//       EVP_MD_CTX_free(ctx);
//    char lhex_now[65] = {0};
//    if (ok)
//       hexdump(md, 32, lhex_now, sizeof(lhex_now));
//    free(line);
//    if (!ok || strcmp(lhex_now, tail.lhash) != 0)
//       return -1;
//    return 0;
// }

// /* ---- 写 seal（安全版：不做 rebuild，只做预检） ---- */
// static int gmseal_write_seal(void)
// {
//    if (g_degraded_mode)
//    {
//       log_msg("In degraded mode: skip writing seal");
//       return 0;
//    }
//    if (safe_pre_seal_check() != 0)
//    {
//       log_msg("safe_pre_seal_check failed; entering degraded mode");
//       enter_degraded_mode("pre-seal check failed");
//       return -1;
//    }
//    return gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
// }

// /* 初始化 */
// static int gmseal_init(void)
// {
//    memset(g_chain, 0, sizeof(g_chain));
//    g_lines = 0;
//    g_offset = 0;
//    g_degraded_mode = 0;

//    (void)open_idx_handle();
//    (void)open_log_handle();

//    int vs = verify_existing_seal();
//    if (vs < 0)
//    {
//       log_msg("Existing seal verify error - aborting");
//       return -1;
//    }

//    if (vs > 0)
//    {
//       /* 首次/归位后：仅当 idx 为空时才允许“全量扫描建立 idx” */
//       unsigned long long idx_lines = idx_count_lines();
//       if (idx_lines == 0)
//       {
//          unsigned char new_chain[32];
//          unsigned long long new_lines = 0, new_off = 0;
//          if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0)
//          {
//             log_msg("rebuild_idx_from_log_atomic failed (first run)");
//             return -1;
//          }
//          if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0)
//          {
//             log_msg("recover_tail_from_idx failed after first-run rebuild");
//             return -1;
//          }
//          log_msg("first-run: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//       }
//       else
//       {
//          /* 已有 idx（例如上次宕机遗留），直接从 idx 尾恢复 */
//          if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0)
//          {
//             log_msg("recover_tail_from_idx failed (existing idx)");
//             return -1;
//          }
//          log_msg("resume from existing idx: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//       }
//    }
//    else
//    {
//       /* seal 已验证通过或进入降级：严格按 idx 尾恢复，不做任何 rebuild 或截短 */
//       if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0)
//       {
//          log_msg("recover_tail_from_idx failed");
//          return -1;
//       }
//       log_msg("verified or degraded: lines=%llu off=%llu (no rebuild)", (unsigned long long)g_lines, (unsigned long long)g_offset);
//    }

//    (void)check_rotation_and_rollover();
//    return 0;
// }

// /* stdin 驱动 */
// static int stdin_event_loop(void)
// {
//    char *line = NULL;
//    size_t cap = 0;
//    ssize_t n;
//    while (g_run && (n = getline(&line, &cap, stdin)) != -1)
//    {
//       (void)n;

//       (void)check_rotation_and_rollover();

//       int rc = process_all_available_lines();
//       if (rc < 0)
//       {
//          trigger_alarm("gmseal plugin processing error - exiting");
//          free(line);
//          return -1;
//       }
//       if (!g_degraded_mode && ALWAYS_REFRESH_SEAL && rc > 0)
//       { /* CHG: 降级态不写 seal */
//          if (gmseal_write_seal() != 0)
//             log_msg("gmseal_write_seal failed");
//       }
//    }
//    if (line)
//       free(line);
//    return 0;
// }

// /* main */
// int main(int argc, char **argv)
// {
//    OpenSSL_add_all_algorithms();
//    ERR_load_crypto_strings();

//    log_open();
//    log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//    if (acquire_singleton_lock() != 0)
//    {
//       if (g_log)
//          fclose(g_log);
//       EVP_cleanup();
//       ERR_free_strings();
//       return 0;
//    }

//    if (gm_crypto_init() != 0)
//    {
//       log_msg("gm_crypto_init failed");
//       fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//       release_singleton_lock();
//       if (g_log)
//          fclose(g_log);
//       EVP_cleanup();
//       ERR_free_strings();
//       return 2;
//    }

//    signal(SIGINT, on_signal);
//    signal(SIGTERM, on_signal);

//    if (gmseal_init() != 0)
//    {
//       log_msg("gmseal_init failed (abort)");
//       gm_crypto_cleanup();
//       release_singleton_lock();
//       if (g_log)
//          fclose(g_log);
//       EVP_cleanup();
//       ERR_free_strings();
//       return 2;
//    }

//    /* 启动后先把当前可读尾部吃完，再写首次 seal（仅预检，不对齐） */
//    {
//       int ate = process_all_available_lines();
//       if (ate < 0)
//       {
//          log_msg("initial tail scan failed");
//       }
//    }
//    if (!g_degraded_mode)
//    {
//       if (gmseal_write_seal() != 0)
//       {
//          log_msg("initial seal write failed");
//       }
//    }
//    else
//    {
//       log_msg("initial: degraded mode active, skip initial seal");
//    }

//    if (stdin_event_loop() != 0)
//    {
//       log_msg("stdin_event_loop aborted due to errors");
//    }

//    (void)gmseal_write_seal(); /* 降级态里会自动跳过 */
//    (void)check_rotation_and_rollover();

//    close_idx_handle();
//    close_log_handle();
//    gm_crypto_cleanup();
//    release_singleton_lock();
//    if (g_log)
//       fclose(g_log);
//    EVP_cleanup();
//    ERR_free_strings();
//    log_msg("audisp-gmseal exited");
//    return 0;
// }


//版本---可疑重写=快照旧文件+归档 idx/seal + 立即切换新代，不再盲目降级）
// #define _GNU_SOURCE
// #include <stdio.h>
// #include <stdlib.h>
// #include <stdarg.h>
// #include <stdint.h>
// #include <unistd.h>
// #include <string.h>
// #include <errno.h>
// #include <signal.h>
// #include <time.h>
// #include <limits.h>
// #include <sys/stat.h>
// #include <sys/file.h>
// #include <sys/types.h>
// #include <fcntl.h>
// #include <sys/time.h>
// #include <dirent.h>
// #include <sys/sysmacros.h>

// #include <openssl/evp.h>
// #include <openssl/pkcs12.h>
// #include <openssl/x509.h>
// #include <openssl/bio.h>
// #include <openssl/buffer.h>
// #include <openssl/err.h>

// #include "../audit-gm/alarm.c"
// #include "../../lib/gm_crypto.h"

// /* 路径配置 */
// #define LOG_PATH "/var/log/audit/audit.log"
// #define IDX_PATH "/var/log/audit/audit.log.idx"
// #define SEAL_PATH "/var/log/audit/audit.log.seal"

// #define P12_PATH "/etc/pki/audit/sm2.p12"
// #define SALT_PATH "/etc/pki/audit/p12_salt"
// #define PASS_PATH "/etc/pki/audit/p12_pass"

// #define SM2_ID "1234567812345678"

// #define SALT_BYTES 16
// #define DERIVE_BYTES 32
// #define DERIVE_ITERATIONS 200000

// /* 每行写 seal（测试保持1，生产可改成周期/阈值） */
// #define ALWAYS_REFRESH_SEAL 1

// /* log for plugin */
// #define GMLOG_DIR "/var/log/audit-gm"
// #define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

// /* singleton lock */
// #define RUN_LOCK "/var/run/audisp-gmseal.lock"

// static FILE *g_log = NULL;
// static int g_run_lock_fd = -1;

// static void log_open(void)
// {
//     mkdir(GMLOG_DIR, 0755);
//     g_log = fopen(GMLOG_FILE, "a");
// }
// static void log_msg(const char *fmt, ...)
// {
//     if (!g_log) return;
//     va_list ap;
//     va_start(ap, fmt);
//     time_t t = time(NULL);
//     char tb[64];
//     strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
//     fprintf(g_log, "[%s] ", tb);
//     vfprintf(g_log, fmt, ap);
//     fprintf(g_log, "\n");
//     fflush(g_log);
//     va_end(ap);
// }
// static int acquire_singleton_lock(void)
// {
//     g_run_lock_fd = open(RUN_LOCK, O_CREAT|O_RDWR, 0644);
//     if (g_run_lock_fd < 0) {
//         log_msg("open run lock %s failed: %s", RUN_LOCK, strerror(errno));
//         return -1;
//     }
//     if (flock(g_run_lock_fd, LOCK_EX | LOCK_NB) != 0) {
//         log_msg("another gmseal instance is running (lock busy), exiting");
//         return -1;
//     }
//     dprintf(g_run_lock_fd, "%ld\n", (long)getpid());
//     return 0;
// }
// static void release_singleton_lock(void)
// {
//     if (g_run_lock_fd >= 0) {
//         flock(g_run_lock_fd, LOCK_UN);
//         close(g_run_lock_fd);
//         unlink(RUN_LOCK); /* 容忍失败 */
//         g_run_lock_fd = -1;
//     }
// }

// static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
// {
//     static const char hex[] = "0123456789abcdef";
//     if (outlen < len * 2 + 1) return;
//     for (size_t i = 0; i < len; i++) {
//         out[i * 2] = hex[(buf[i] >> 4) & 0xF];
//         out[i * 2 + 1] = hex[buf[i] & 0xF];
//     }
//     out[len * 2] = 0;
// }

// static char *b64_encode(const unsigned char *buf, size_t len)
// {
//     BIO *b64 = BIO_new(BIO_f_base64());
//     BIO *mem = BIO_new(BIO_s_mem());
//     if (!b64 || !mem) { if (b64) BIO_free(b64); if (mem) BIO_free(mem); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b64, mem);
//     if (BIO_write(b64, buf, (int)len) <= 0) { BIO_free_all(b64); return NULL; }
//     (void)BIO_flush(b64);
//     BUF_MEM *bptr = NULL;
//     BIO_get_mem_ptr(b64, &bptr);
//     char *out = malloc(bptr->length + 1);
//     if (!out) { BIO_free_all(b64); return NULL; }
//     memcpy(out, bptr->data, bptr->length);
//     out[bptr->length] = 0;
//     BIO_free_all(b64);
//     return out;
// }

// static unsigned char *b64_decode(const char *b64, size_t *olen)
// {
//     BIO *b = BIO_new(BIO_f_base64());
//     BIO *m = BIO_new_mem_buf(b64, -1);
//     if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
// #ifdef BIO_FLAGS_BASE64_NO_NL
//     BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
// #endif
//     BIO_push(b, m);
//     size_t cap = strlen(b64) * 3 / 4 + 16;
//     unsigned char *buf = malloc(cap);
//     int n = BIO_read(b, buf, (int)cap);
//     BIO_free_all(b);
//     if (n <= 0) { free(buf); return NULL; }
//     *olen = (size_t)n;
//     return buf;
// }

// static int sm3_file_hex(const char *path, char out_hex[65])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *f = fopen(path, "rb");
//     if (!f) return -1;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx) { fclose(f); return -1; }
//     if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     unsigned char buf[4096];
//     size_t n;
//     while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
//         if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
//     }
//     fclose(f);
//     unsigned char md[32]; unsigned int mdlen = 0;
//     if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
//     EVP_MD_CTX_free(ctx);
//     hexdump(md, 32, out_hex, 65);
//     return 0;
// }

// static size_t normalize_line(char *buf, size_t len)
// {
//     while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) len--;
//     return len;
// }

// static unsigned long long count_file_lines_simple(const char *path)
// {
//     FILE *f = fopen(path, "rb");
//     if (!f) return 0;
//     char *line = NULL; size_t cap = 0; ssize_t n; unsigned long long ln = 0;
//     while ((n = getline(&line, &cap, f)) != -1) ln++;
//     free(line); fclose(f); return ln;
// }

// /* ------------ idx helpers ------------ */
// typedef struct {
//     unsigned long long i;
//     unsigned long long off;
//     char lhash[65];
//     char chash[65];
// } IdxEntry;

// typedef struct {
//     IdxEntry *entries;
//     size_t count;
// } IdxArray;

// static void free_idx_array(IdxArray *a) {
//     if (!a) return;
//     free(a->entries);
//     a->entries = NULL; a->count = 0;
// }

// static int load_idx_entries(const char *path, IdxArray *out) {
//     memset(out,0,sizeof(*out));
//     FILE *f = fopen(path,"r");
//     if (!f) return -1;
//     char buf[1024];
//     IdxEntry *arr = NULL; size_t cnt = 0, capv = 0;
//     while (fgets(buf,sizeof(buf),f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long ii=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&ii,&off,lhex,chex)==4) {
//             if (cnt == capv) {
//                 size_t nc = capv ? capv*2 : 256;
//                 IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
//                 if (!p) { free(arr); fclose(f); return -1; }
//                 arr = p; capv = nc;
//             }
//             arr[cnt].i = ii; arr[cnt].off = off;
//             strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
//             strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
//             cnt++;
//         }
//     }
//     fclose(f);
//     out->entries = arr; out->count = cnt;
//     return 0;
// }

// /* 找可信前缀（idx 与 log 不一致时）—— 保留，但我们不再自动截短使用它 */
// static int find_trusted_prefix(const char *logpath, const char *idxpath,
//                               unsigned long long *out_trusted_lines,
//                               unsigned char out_chain[32],
//                               unsigned long long *out_offset)
// {
//     IdxArray idx = {0};
//     if (load_idx_entries(idxpath, &idx) != 0) {
//         if (out_trusted_lines) *out_trusted_lines = 0;
//         if (out_chain) memset(out_chain,0,32);
//         if (out_offset) *out_offset = 0;
//         free_idx_array(&idx);
//         return 0;
//     }

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) { free_idx_array(&idx); return -1; }

//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) { free_idx_array(&idx); return -1; }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     unsigned char H[32]; memset(H,0,sizeof(H));
//     unsigned long long ln = 0;
//     size_t idx_pos = 0;
//     unsigned long long last_good_i = 0, last_good_off = 0;
//     unsigned char last_good_chain[32]; memset(last_good_chain,0,sizeof(last_good_chain));

//     while ((n = getline(&line, &cap, lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line, (size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned int mdlen=0;
//         if (!ctx) { free(line); fclose(lf); free_idx_array(&idx); return -1; }
//         if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1 ||
//             EVP_DigestUpdate(ctx, H, sizeof(H)) != 1 ||
//             EVP_DigestUpdate(ctx, line, normlen) != 1 ||
//             EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) {
//             EVP_MD_CTX_free(ctx); free(line); fclose(lf); free_idx_array(&idx);
//             return -1;
//         }
//         EVP_MD_CTX_free(ctx);

//         if (idx_pos < idx.count) {
//             IdxEntry *e = &idx.entries[idx_pos];
//             if (e->i == ln) {
//                 char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                 if (strcmp(hhex, e->chash) == 0) {
//                     last_good_i = e->i;
//                     last_good_off = e->off;
//                     memcpy(last_good_chain, H, 32);
//                     idx_pos++;
//                     continue;
//                 } else break;
//             } else if (e->i < ln) {
//                 while (idx_pos < idx.count && idx.entries[idx_pos].i < ln) idx_pos++;
//                 if (idx_pos < idx.count && idx.entries[idx_pos].i == ln) {
//                     char hhex[65]; hexdump(H,32,hhex,sizeof(hhex));
//                     if (strcmp(hhex, idx.entries[idx_pos].chash) == 0) {
//                         last_good_i = idx.entries[idx_pos].i;
//                         last_good_off = idx.entries[idx_pos].off;
//                         memcpy(last_good_chain, H, 32);
//                         idx_pos++;
//                         continue;
//                     } else break;
//                 } else break;
//             } else break;
//         } else break;
//     }

//     free(line); fclose(lf);
//     if (out_trusted_lines) *out_trusted_lines = last_good_i;
//     if (out_offset) *out_offset = last_good_off;
//     if (out_chain) memcpy(out_chain, last_good_chain, 32);
//     free_idx_array(&idx);
//     if (last_good_i > 0) return 1;
//     return 0;
// }

// /* -------- seal 解析 -------- */
// struct seal {
//     char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
// };

// static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
// static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}

// static int parse_seal_file(const char *seal_path, struct seal *S)
// {
//     memset(S,0,sizeof(*S));
//     FILE *f = fopen(seal_path,"r");
//     if (!f) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while ((n = getline(&line,&cap,f)) != -1) {
//         if (line[0]=='#' || line[0]==0) continue;
//         trimnl(line);
//         char *eq = strchr(line,'=');
//         if (!eq) continue;
//         *eq = 0;
//         char *k = trimws(line);
//         char *v = trimws(eq+1);
//         if (strcmp(k,"version")==0) S->version=strdup(v);
//         else if (strcmp(k,"hash")==0) S->hash=strdup(v);
//         else if (strcmp(k,"sign")==0) S->sign=strdup(v);
//         else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
//         else if (strcmp(k,"path")==0) S->path=strdup(v);
//         else if (strcmp(k,"records")==0) S->records=strdup(v);
//         else if (strcmp(k,"chain")==0) S->chain=strdup(v);
//         else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
//         else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
//         else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
//         else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
//     }
//     free(line); fclose(f);
//     if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
//         S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
//     return -1;
// }
// static void free_seal(struct seal *S)
// {
//     free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
//     free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
//     free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
// }
// static char *build_canon_from_seal(struct seal *S)
// {
//     char *buf = NULL;
//     if (asprintf(&buf,
//         "version=%s\n"
//         "hash=%s\n"
//         "sign=%s\n"
//         "sm2_id=%s\n"
//         "path=%s\n"
//         "records=%s\n"
//         "chain=%s\n"
//         "key_fpr=%s\n"
//         "index_sm3=%s\n",
//         S->version,S->hash,S->sign,S->sm2_id,S->path,S->records,S->chain,S->key_fpr,S->index_sm3) < 0) return NULL;
//     return buf;
// }

// static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     unsigned char H[32] = {0};
//     unsigned long long scanned = 0;
//     FILE *fp = fopen(path,"rb");
//     if (!fp) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     while (scanned < limit_lines && (n = getline(&line,&cap,fp)) != -1) {
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         if (!ctx) { fclose(fp); free(line); return -1; }
//         int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
//                   EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
//                   EVP_DigestUpdate(ctx, line, normlen) == 1);
//         unsigned int mdlen = 0;
//         if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
//         EVP_MD_CTX_free(ctx);
//         scanned++;
//     }
//     free(line); fclose(fp); memcpy(out32,H,32); return 0;
// }

// /* ---------- idx handle ---------- */
// static int g_idx_fd = -1;
// static FILE *g_idx = NULL;

// static void close_idx_handle(void) {
//     if (g_idx) { fclose(g_idx); g_idx = NULL; }
//     if (g_idx_fd >= 0) { close(g_idx_fd); g_idx_fd = -1; }
// }
// static int open_idx_handle(void) {
//     if (g_idx_fd >= 0) return 0;
//     int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
//     if (fd < 0) {
//         log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
//         return -1;
//     }
//     FILE *f = fdopen(dup(fd), "a+");
//     if (!f) { log_msg("open_idx_handle: fdopen failed: %s", strerror(errno)); close(fd); return -1; }
//     g_idx_fd = fd;
//     g_idx = f;
//     fchmod(g_idx_fd, 0600);
//     return 0;
// }

// static int g_degraded_mode = 0;

// static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex) {
//     if (g_degraded_mode) { /* 降级态禁止写入 */
//         log_msg("append_idx_line_atomic: degraded mode, skip append");
//         return 0;
//     }
//     if (g_idx_fd < 0) {
//         log_msg("append_idx_line_atomic: idx fd not open");
//         return -1;
//     }
//     if (flock(g_idx_fd, LOCK_EX) != 0) {
//         log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
//     }
//     int ret = 0;
//     ssize_t w = dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n",
//                         (unsigned long long)i, (unsigned long long)off, lhex, chex);
//     if (w < 0) { log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno)); ret = -1; }
//     else fsync(g_idx_fd);
//     flock(g_idx_fd, LOCK_UN);
//     return ret;
// }

// /* *** 全量重建：仅允许首次部署且 idx 为空时使用 *** */
// static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath,
//                                        unsigned char out_chain[32],
//                                        unsigned long long *out_lines,
//                                        unsigned long long *out_offset)
// {
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;
//     FILE *lf = fopen(logpath,"rb");
//     if (!lf) return -1;
//     char *line = NULL; size_t cap=0; ssize_t n;
//     unsigned long long ln=0, offset=0;
//     unsigned char H[32]; memset(H,0,sizeof(H));

//     close_idx_handle();

//     char tmppath[PATH_MAX];
//     snprintf(tmppath,sizeof(tmppath), "%s.tmp", idxpath);
//     FILE *tf = fopen(tmppath, "w");
//     if (!tf) { fclose(lf); return -1; }
//     chmod(tmppath, 0600);

//     while ((n = getline(&line,&cap,lf)) != -1) {
//         ln++;
//         size_t normlen = normalize_line(line,(size_t)n);
//         EVP_MD_CTX *c1 = EVP_MD_CTX_new(); unsigned char L[32]; unsigned int lm=0;
//         if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL)!=1 || EVP_DigestUpdate(c1, line, normlen)!=1 || EVP_DigestFinal_ex(c1,L,&lm)!=1 || lm!=32) {
//             if (c1) EVP_MD_CTX_free(c1);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new(); unsigned char newH[32]; unsigned int hm=0;
//         if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL)!=1 || EVP_DigestUpdate(c2, H, sizeof(H))!=1 || EVP_DigestUpdate(c2, line, normlen)!=1 || EVP_DigestFinal_ex(c2, newH, &hm)!=1 || hm!=32) {
//             if (c2) EVP_MD_CTX_free(c2);
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);
//         memcpy(H, newH, 32);

//         char lhex[65], chex[65];
//         hexdump(L,32,lhex,sizeof(lhex));
//         hexdump(H,32,chex,sizeof(chex));
//         if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
//                     (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
//             fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
//         }
//         offset += (unsigned long long)n;
//     }
//     free(line); fclose(lf);
//     fflush(tf); fsync(fileno(tf)); fclose(tf);
//     if (rename(tmppath, idxpath) != 0) { unlink(tmppath); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("rebuild: open_idx_handle failed after rename"); return -1; }
//     if (out_chain) memcpy(out_chain,H,32);
//     if (out_lines) *out_lines = ln;
//     if (out_offset) *out_offset = offset;
//     return 0;
// }

// static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
// {
//     if (g_degraded_mode) { /* 降级态不允许截断 */
//         log_msg("truncate_idx_to_i_atomic: degraded mode, skip truncation");
//         return 0;
//     }
//     close_idx_handle();
//     FILE *in = fopen(idxpath,"r");
//     if (!in) {
//         if (keep_i == 0) {
//             FILE *out = fopen(idxpath,"w");
//             if (!out) return -1;
//             fclose(out);
//             chmod(idxpath, 0600);
//             if (open_idx_handle() != 0) return -1;
//             return 0;
//         }
//         return -1;
//     }
//     char tmp[PATH_MAX];
//     snprintf(tmp,sizeof(tmp), "%s.trunc", idxpath);
//     FILE *out = fopen(tmp, "w");
//     if (!out) { fclose(in); return -1; }
//     char buf[2048];
//     while (fgets(buf,sizeof(buf),in)) {
//         if (buf[0]=='#') { if (fputs(buf,out) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; } continue; }
//         unsigned long long i=0, off=0; char lhex[256]={0}, chex[256]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%255s chash=%255s",&i,&off,lhex,chex)==4) {
//             if (i <= keep_i) {
//                 if (fprintf(out,"i=%llu off=%llu lhash=%s chash=%s\n",i,off,lhex,chex) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; }
//             } else break;
//         }
//     }
//     fclose(in);
//     fflush(out); fsync(fileno(out)); fclose(out);
//     if (rename(tmp, idxpath) != 0) { unlink(tmp); return -1; }
//     chmod(idxpath, 0600);
//     if (open_idx_handle() != 0) { log_msg("truncate: open_idx_handle failed after rename"); return -1; }
//     return 0;
// }

// /* ---------- global state ---------- */
// static unsigned char g_chain[32];
// static unsigned long long g_lines = 0;
// static unsigned long long g_offset = 0;
// static int g_run = 1;

// /* 持久持有的 audit.log 句柄与 inode，用于轮转/可疑重写检测 */
// static int   g_log_fd  = -1;
// static FILE *g_log_fp  = NULL;
// static dev_t g_log_dev = 0;
// static ino_t g_log_ino = 0;

// static void on_signal(int sig) { if (sig == SIGINT || sig == SIGTERM) g_run = 0; }

// /* 进入降级模式（只读、报警、保留现状） */
// static void enter_degraded_mode(const char *why)
// {
//     if (!g_degraded_mode) {
//         g_degraded_mode = 1;
//         log_msg("ENTER DEGRADED MODE: %s", why ? why : "unknown");
//         // trigger_alarm("gmseal degraded mode: %s", why ? why : "unknown");
//     }
// }

// /* 打开/关闭当前 log 的句柄（持久） */
// static int open_log_handle(void)
// {
//     if (g_log_fd >= 0) return 0;
//     g_log_fd = open(LOG_PATH, O_RDONLY | O_CLOEXEC);
//     if (g_log_fd < 0) {
//         log_msg("open_log_handle: open %s failed: %s", LOG_PATH, strerror(errno));
//         return -1;
//     }
//     struct stat st;
//     if (fstat(g_log_fd, &st) != 0) {
//         log_msg("open_log_handle: fstat failed: %s", strerror(errno));
//         close(g_log_fd); g_log_fd = -1;
//         return -1;
//     }
//     g_log_dev = st.st_dev;
//     g_log_ino = st.st_ino;
//     g_log_fp = fdopen(dup(g_log_fd), "rb");
//     if (!g_log_fp) {
//         log_msg("open_log_handle: fdopen failed: %s", strerror(errno));
//         close(g_log_fd); g_log_fd = -1;
//         return -1;
//     }
//     return 0;
// }
// static void close_log_handle(void)
// {
//     if (g_log_fp) { fclose(g_log_fp); g_log_fp = NULL; }
//     if (g_log_fd >= 0) { close(g_log_fd); g_log_fd = -1; }
//     g_log_dev = 0; g_log_ino = 0;
// }

// /* NEW: 把当前 g_log_fd 指向的文件完整复制到 dst_path */
// static int snapshot_current_logfd_to(const char *dst_path)
// {
//     if (g_log_fd < 0) return -1;

//     int src = dup(g_log_fd);
//     if (src < 0) {
//         log_msg("snapshot: dup old log fd failed: %s", strerror(errno));
//         return -1;
//     }

//     int out = open(dst_path, O_WRONLY|O_CREAT|O_TRUNC, 0600);
//     if (out < 0) {
//         log_msg("snapshot: open %s failed: %s", dst_path, strerror(errno));
//         close(src);
//         return -1;
//     }

//     if (lseek(src, 0, SEEK_SET) < 0) {
//         log_msg("snapshot: lseek src failed: %s", strerror(errno));
//         close(src); close(out);
//         return -1;
//     }

//     char buf[1<<20];
//     while (1) {
//         ssize_t r = read(src, buf, sizeof(buf));
//         if (r == 0) break;
//         if (r < 0) {
//             if (errno == EINTR) continue;
//             log_msg("snapshot: read failed: %s", strerror(errno));
//             close(src); close(out);
//             return -1;
//         }
//         char *p = buf;
//         ssize_t left = r;
//         while (left > 0) {
//             ssize_t w = write(out, p, left);
//             if (w < 0) {
//                 if (errno == EINTR) continue;
//                 log_msg("snapshot: write failed: %s", strerror(errno));
//                 close(src); close(out);
//                 return -1;
//             }
//             p += w; left -= w;
//         }
//     }

//     fsync(out);
//     close(src);
//     close(out);
//     chmod(dst_path, 0600);
//     log_msg("snapshot: created %s", dst_path);
//     return 0;
// }

// /* 从 idx 尾恢复 */
// static int recover_tail_from_idx(unsigned long long *out_i,
//                                  unsigned long long *out_off,
//                                  unsigned char out_chain[32])
// {
//     FILE *ix = fopen(IDX_PATH, "r");
//     if (!ix) return -1;
//     char buf[1024]; unsigned long long last_i = 0, last_off = 0;
//     char last_ch[65] = {0};
//     while (fgets(buf, sizeof(buf), ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//             last_i = i; last_off = off; strncpy(last_ch, c, sizeof(last_ch)-1);
//         }
//     }
//     fclose(ix);
//     if (out_i) *out_i = last_i;
//     if (out_off) *out_off = last_off;
//     if (out_chain) {
//         if (last_i == 0) { memset(out_chain,0,32); return 0; }
//         for (int i=0;i<32;i++){
//             unsigned int hi,lo;
//             if (sscanf(&last_ch[i*2], "%1x%1x", &hi,&lo) != 2) return -1;
//             out_chain[i]=(hi<<4)|lo;
//         }
//     }
//     return 0;
// }

// /* 读取 idx 最后一条的完整 4 元组 */
// static int read_last_idx_entry(IdxEntry *out)
// {
//     if (!out) return -1;
//     memset(out,0,sizeof(*out));
//     FILE *ix = fopen(IDX_PATH,"r");
//     if (!ix) return -1;
//     char buf[1024];
//     int have = 0;
//     while (fgets(buf,sizeof(buf),ix)) {
//         if (buf[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             out->i = i; out->off = off;
//             strncpy(out->lhash, lhex, sizeof(out->lhash)-1);
//             strncpy(out->chash, chex, sizeof(out->chash)-1);
//             have = 1;
//         }
//     }
//     fclose(ix);
//     return have ? 0 : -1;
// }

// /* 仅用于统计 idx 行数（判断是否空 idx） */
// static unsigned long long idx_count_lines(void)
// {
//     FILE *f = fopen(IDX_PATH, "r");
//     if (!f) return 0;
//     char buf[256];
//     unsigned long long cnt = 0;
//     while (fgets(buf,sizeof(buf),f)) {
//         if (buf[0] == '#') continue;
//         unsigned long long i=0, off=0; char l[128], c[128];
//         if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) cnt++;
//     }
//     fclose(f);
//     return cnt;
// }

// /* 旧文件 drain 到 EOF */
// static int drain_old_file_to_eof(void)
// {
//     if (!g_log_fp) return 0;
//     if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
//         log_msg("drain_old_file_to_eof: fseeko failed: %s", strerror(errno));
//         return -1;
//     }
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     int cnt = 0;
//     while ((n = getline(&line, &cap, g_log_fp)) > 0) {
//         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//         if (!sm3) { free(line); return -1; }
//         size_t normlen = normalize_line(line,(size_t)n);

//         unsigned char lhash[32], cand_chain[32];
//         unsigned int lm=0, hm=0;

//         EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//         if (!c1) { free(line); return -1; }
//         if (EVP_DigestInit_ex(c1, sm3, NULL)!=1 ||
//             EVP_DigestUpdate(c1, line, normlen)!=1 ||
//             EVP_DigestFinal_ex(c1, lhash, &lm)!=1 || lm!=32) {
//             EVP_MD_CTX_free(c1); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c1);

//         EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//         if (!c2) { free(line); return -1; }
//         if (EVP_DigestInit_ex(c2, sm3, NULL)!=1 ||
//             EVP_DigestUpdate(c2, g_chain, sizeof(g_chain))!=1 ||
//             EVP_DigestUpdate(c2, line, normlen)!=1 ||
//             EVP_DigestFinal_ex(c2, cand_chain, &hm)!=1 || hm!=32) {
//             EVP_MD_CTX_free(c2); free(line); return -1;
//         }
//         EVP_MD_CTX_free(c2);

//         char lhex[65], chex[65];
//         hexdump(lhash,32,lhex,sizeof(lhex));
//         hexdump(cand_chain,32,chex,sizeof(chex));

//         if (g_idx_fd < 0 && open_idx_handle() != 0) {
//             log_msg("drain_old_file_to_eof: open_idx_handle failed");
//             free(line); return -1;
//         }
//         if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
//             log_msg("drain_old_file_to_eof: append idx failed");
//             free(line); return -1;
//         }
//         memcpy(g_chain, cand_chain, 32);
//         g_lines++;
//         g_offset += (unsigned long long)n;
//         cnt++;
//     }
//     free(line);
//     return cnt;
// }

// /* 写 seal 的通用函数：为指定 log/idx 输出到指定 seal 文件 */
// static int gmseal_write_seal_for(const char *log_path_for_canon,
//                                  const char *idx_path_for_hash,
//                                  const char *seal_out_path)
// {
//     if (g_degraded_mode) {
//         log_msg("In degraded mode: skip writing seal_for(%s)", log_path_for_canon);
//         return 0;
//     }

//     EVP_PKEY *pkey = NULL; X509 *cert = NULL;

//     FILE *pf = fopen(PASS_PATH,"r");
//     if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
//     char *secret = NULL; size_t cap=0; ssize_t n = getline(&secret, &cap, pf); fclose(pf);
//     if (n <= 0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
//     while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n]=0;

//     unsigned char salt[SALT_BYTES];
//     FILE *sf = fopen(SALT_PATH,"r");
//     if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     char saltbuf[256]; if (!fgets(saltbuf,sizeof(saltbuf),sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     fclose(sf);
//     size_t slen = strlen(saltbuf); while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
//     if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
//     for (size_t i=0;i<SALT_BYTES;i++){ unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x", &hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; } salt[i] = (hi<<4)|lo; }

//     unsigned char derived[DERIVE_BYTES];
//     if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived)) {
//         log_msg("PBKDF2-SM3 failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
//     }
//     char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
//     OPENSSL_cleanse(secret, strlen(secret)); free(secret);

//     FILE *pf2 = fopen(P12_PATH,"rb");
//     if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
//     PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
//     if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
//     if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) { log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1; }
//     PKCS12_free(p12);

//     unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
//     if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der);
//     char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

//     char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

//     char idx_sm3[65] = {0};
//     if (sm3_file_hex(idx_path_for_hash, idx_sm3) != 0) { EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed for %s", idx_path_for_hash); return -1; }

//     char *canon = NULL;
//     if (asprintf(&canon, "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
//         SM2_ID, log_path_for_canon, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     unsigned char *sig=NULL; size_t siglen = 0;
//     if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
//         log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
//     }
//     char *sigb64 = b64_encode(sig, siglen);
//     OPENSSL_free(sig);

//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     char *certb64 = NULL;
//     if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); } else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

//     EVP_PKEY_free(pkey); X509_free(cert);

//     char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp), "%s.tmp", seal_out_path);
//     FILE *sfw = fopen(tmp, "w");
//     if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
//     fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
//     fprintf(sfw, "cert_der_base64=%s\n", certb64);
//     fprintf(sfw, "sig_base64=%s\n", sigb64);
//     fclose(sfw);
//     chmod(tmp, 0600);
//     if (rename(tmp, seal_out_path) != 0) { log_msg("rename seal tmp -> %s failed: %s", seal_out_path, strerror(errno)); unlink(tmp); free(canon); free(sigb64); free(certb64); return -1; }
//     free(canon); free(sigb64); free(certb64);

//     log_msg("seal written: %s (records=%llu path=%s)", seal_out_path, (unsigned long long)g_lines, log_path_for_canon);
//     return 0;
// }

// /* 把当前 .idx/.seal 归档到已轮转的旧文件名 */
// static int archive_current_idx_and_seal_to(const char *archived_log_path)
// {
//     char idx_dst[PATH_MAX], seal_dst[PATH_MAX];
//     snprintf(idx_dst,  sizeof(idx_dst),  "%s.idx",  archived_log_path);
//     snprintf(seal_dst, sizeof(seal_dst), "%s.seal", archived_log_path);

//     close_idx_handle();

//     if (rename(IDX_PATH, idx_dst) != 0) {
//         log_msg("archive: rename %s -> %s failed: %s", IDX_PATH, idx_dst, strerror(errno));
//         return -1;
//     }
//     chmod(idx_dst, 0600);

//     if (gmseal_write_seal_for(archived_log_path, idx_dst, seal_dst) != 0) {
//         log_msg("archive: write seal_for(%s) failed", archived_log_path);
//         return -1;
//     }
//     return 0;
// }

// /* 用 inode 在 /var/log/audit 查找已轮转的旧文件名 */
// static int find_rotated_name_by_inode(char out_path[PATH_MAX], dev_t dev, ino_t ino)
// {
//     DIR *d = opendir("/var/log/audit");
//     if (!d) return -1;
//     struct dirent *de;
//     struct stat st;
//     while ((de = readdir(d))) {
//         if (strncmp(de->d_name, "audit.log", 9) != 0) continue;
//         char cand[PATH_MAX];
//         snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
//         if (stat(cand, &st) != 0) continue;
//         if (st.st_dev == dev && st.st_ino == ino) {
//             strncpy(out_path, cand, PATH_MAX-1);
//             out_path[PATH_MAX-1] = 0;
//             closedir(d);
//             return 0;
//         }
//     }
//     closedir(d);
//     return -1;
// }

// /* 检测轮转并执行归档与切换（严格区分“合法轮转”和“可疑重写”） */
// static int check_rotation_and_rollover(void)
// {
//     if (g_log_fd < 0) return open_log_handle();

//     struct stat st_now;
//     if (stat(LOG_PATH, &st_now) != 0) {
//         return 0;
//     }
//     if (st_now.st_dev == g_log_dev && st_now.st_ino == g_log_ino) return 0;

//     /* inode 变了：先把旧 FD 指向的旧内容彻底 drain 完成索引 */
//     log_msg("detected inode change: old ino=%ju -> new ino=%ju",
//             (uintmax_t)g_log_ino, (uintmax_t)st_now.st_ino);

//     (void)drain_old_file_to_eof();

//     /* 判断是否真轮转：能否找到旧 inode 对应的归档文件名 */
//     char archived_log[PATH_MAX]={0};
//     if (find_rotated_name_by_inode(archived_log, g_log_dev, g_log_ino) == 0) {
//         /* 合法轮转 —— 归档 idx/seal 到旧名，再切换新代 */
//         log_msg("rotation confirmed: archived file = %s", archived_log);
//         (void)archive_current_idx_and_seal_to(archived_log);

//         close_log_handle();
//         if (open_log_handle() != 0) {
//             log_msg("rotation: open new audit.log failed");
//             return -1;
//         }

//         /* 新代从 0 开始（append-only） */
//         if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
//             log_msg("rotation: truncate new idx failed");
//             return -1;
//         }
//         memset(g_chain,0,sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;

//         g_degraded_mode = 0; // 真轮转可清除降级

//         /* 可选：写一版“空链”seal */
//         (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
//         return 0;
//     }

//     /* 可疑重写（例如 sed -i）：对旧 FD 快照 -> 归档 idx/seal -> 切新代（不降级） */
//     {
//         char snap_path[PATH_MAX];
//         time_t now = time(NULL);
//         snprintf(snap_path, sizeof(snap_path),
//                  "/var/log/audit/audit.log.suspected-%ju-%ld",
//                  (uintmax_t)g_log_ino, (long)now);

//         if (snapshot_current_logfd_to(snap_path) != 0) {
//             log_msg("suspicious rewrite: snapshot old logfd failed; entering degraded mode");
//             enter_degraded_mode("cannot snapshot old logfd");
//             close_log_handle();
//             (void)open_log_handle();
//             return 0;
//         }

//         if (archive_current_idx_and_seal_to(snap_path) != 0) {
//             log_msg("suspicious rewrite: archive idx/seal to %s failed; entering degraded mode", snap_path);
//             enter_degraded_mode("archive idx/seal to snapshot failed");
//             close_log_handle();
//             (void)open_log_handle();
//             return 0;
//         }

//         close_log_handle();
//         if (open_log_handle() != 0) {
//             log_msg("suspicious rewrite: open new audit.log failed");
//             return -1;
//         }

//         if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
//             log_msg("suspicious rewrite: truncate new idx failed");
//             return -1;
//         }
//         memset(g_chain, 0, sizeof(g_chain));
//         g_lines = 0;
//         g_offset = 0;

//         g_degraded_mode = 0; /* 不降级，继续实时对齐新代 */

//         (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
//         log_msg("suspicious rewrite: snapshotted old log to %s and started new generation", snap_path);
//         return 0;
//     }
// }

// /* 如果发现现有 seal 与当前文件不匹配，尝试把它“归位”到已轮转旧文件 */
// static int try_rehome_stale_seal_to_rotated_file(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) return -1;
//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) return -1;
//     unsigned long long N = strtoull(S.records ? S.records : "0", NULL, 10);
//     if (N == 0 || !S.chain) { free_seal(&S); return -1; }

//     DIR *d = opendir("/var/log/audit"); if (!d) { free_seal(&S); return -1; }
//     struct dirent *de; int ok = -1;
//     while ((de = readdir(d))) {
//         if (strncmp(de->d_name, "audit.log.", 10) != 0) continue;
//         char cand[PATH_MAX]; snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
//         unsigned char chainR[32]; if (sm3_chain_file_limit_local(cand, N, chainR) != 0) continue;
//         char chx[65]; hexdump(chainR, 32, chx, sizeof(chx));
//         if (strcmp(chx, S.chain) == 0) {
//             (void)archive_current_idx_and_seal_to(cand);
//             ok = 0; break;
//         }
//     }
//     closedir(d); free_seal(&S);
//     if (ok == 0) {
//         (void)truncate_idx_to_i_atomic(IDX_PATH, 0);
//         memset(g_chain,0,sizeof(g_chain)); g_lines=0; g_offset=0;
//         g_degraded_mode = 0;
//         (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
//     }
//     return ok;
// }

// /* 验证现有 seal：一致则恢复状态；不一致则尝试归位；否则进入降级但不截断 */
// static int verify_existing_seal(void)
// {
//     if (access(SEAL_PATH, R_OK) != 0) {
//         log_msg("no existing seal: treat as first-run");
//         return 1; /* 首次无 seal */
//     }

//     struct seal S;
//     if (parse_seal_file(SEAL_PATH, &S) != 0) {
//         log_msg("parse existing seal failed");
//         trigger_alarm("Existing seal is not parseable - manual recovery needed");
//         enter_degraded_mode("existing seal parse failed");
//         free_seal(&S);
//         return 0;
//     }
//     if (strcmp(S.hash,"sm3")!=0 || strcmp(S.sign,"sm2")!=0 || strcmp(S.sm2_id, SM2_ID)!=0) {
//         log_msg("existing seal algorithm mismatch");
//         trigger_alarm("Existing seal algorithm mismatch");
//         enter_degraded_mode("existing seal algorithm mismatch");
//         free_seal(&S);
//         return 0;
//     }

//     size_t cert_len=0; unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
//     if (!cert_der) { free_seal(&S); trigger_alarm("Existing seal cert b64 decode failed"); enter_degraded_mode("seal cert b64 decode failed"); return 0; }
//     const unsigned char *p = cert_der; X509 *cert = d2i_X509(NULL, &p, cert_len);
//     free(cert_der);
//     if (!cert) { free_seal(&S); trigger_alarm("Existing seal d2i_X509 failed"); enter_degraded_mode("seal d2i_X509 failed"); return 0; }
//     EVP_PKEY *pub = X509_get_pubkey(cert);
//     if (!pub) { X509_free(cert); free_seal(&S); trigger_alarm("Existing seal get pubkey failed"); enter_degraded_mode("seal get pubkey failed"); return 0; }
//     EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pub);
//     if (!eckey || EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != NID_sm2) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal cert is not SM2");
//         enter_degraded_mode("seal cert not SM2");
//         return 0;
//     }

//     unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
//     if (der2len <= 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal i2d_X509 failed");
//         enter_degraded_mode("seal i2d_X509 failed"); return 0;
//     }
//     unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL)!=1 ||
//         EVP_DigestUpdate(ctx, der2, der2len) != 1 ||
//         EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
//         if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal fingerprint digest failed");
//         enter_degraded_mode("seal fingerprint digest failed");
//         return 0;
//     }
//     EVP_MD_CTX_free(ctx); OPENSSL_free(der2);
//     char fpr_hex[65]; hexdump(mdv,mdlen,fpr_hex,sizeof(fpr_hex));
//     if (strcmp(fpr_hex, S.key_fpr) != 0) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal certificate fingerprint mismatch");
//         enter_degraded_mode("seal key_fpr mismatch");
//         return 0;
//     }

//     char *canon = build_canon_from_seal(&S);
//     if (!canon) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("asprintf canon failed"); enter_degraded_mode("seal canon build failed"); return 0; }
//     size_t siglen = 0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
//     if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Existing seal sig b64 decode failed"); enter_degraded_mode("seal sig b64 decode failed"); return 0; }
//     int v = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
//     free(sig); free(canon);
//     if (v != 1) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing seal signature invalid");
//         enter_degraded_mode("seal signature invalid");
//         return 0;
//     }

//     unsigned long long N = strtoull(S.records, NULL, 10);

//     if (N > 0) {
//         unsigned char chainR[32];
//         if (sm3_chain_file_limit_local(S.path, N, chainR) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute chain from audit.log for existing seal");
//             enter_degraded_mode("cannot compute chain from audit.log");
//             return 0;
//         }
//         char chx[65]; hexdump(chainR,32,chx,sizeof(chx));
//         if (strcmp(chx, S.chain) != 0) {
//             log_msg("Existing seal chain mismatch; trying to rehome stale seal...");
//             if (try_rehome_stale_seal_to_rotated_file() == 0) {
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 log_msg("stale seal rehomed to rotated file; treating as first-run for current log");
//                 return 1;
//             }
//             log_msg("Existing seal chain mismatch and cannot rehome -> enter degraded mode, keep idx/seal");
//             enter_degraded_mode("existing seal chain mismatch");
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }
//     }

//     if (access(IDX_PATH, R_OK) == 0) {
//         char now_sm3[65];
//         if (sm3_file_hex(IDX_PATH, now_sm3) != 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot compute idx sm3");
//             enter_degraded_mode("cannot compute idx sm3");
//             return 0;
//         }
//         if (strcmp(now_sm3, S.index_sm3) != 0) {
//             log_msg("Index file hash mismatch; trying to rehome stale seal...");
//             if (try_rehome_stale_seal_to_rotated_file() == 0) {
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 log_msg("stale seal rehomed; treating as first-run");
//                 return 1;
//             }
//             log_msg("Index file hash mismatch and cannot rehome -> enter degraded mode, keep idx/seal");
//             enter_degraded_mode("index file sm3 mismatch");
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             return 0;
//         }

//         FILE *ix = fopen(IDX_PATH,"r");
//         if (ix) {
//             char buf[1024]; unsigned long long last_i = 0; char last_ch[65] = {0};
//             while (fgets(buf,sizeof(buf),ix)) {
//                 if (buf[0]=='#') continue;
//                 unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
//                 if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
//                     last_i = i; strncpy(last_ch, c, sizeof(last_ch)-1);
//                 }
//             }
//             fclose(ix);
//             if (last_i != N || strcmp(last_ch, S.chain) != 0) {
//                 log_msg("seal vs idx tail mismatch; trying to rehome stale seal...");
//                 if (try_rehome_stale_seal_to_rotated_file() == 0) {
//                     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                     log_msg("stale seal rehomed; treating as first-run");
//                     return 1;
//                 }
//                 log_msg("seal vs idx tail mismatch and cannot rehome -> enter degraded mode, keep idx/seal");
//                 enter_degraded_mode("seal vs idx tail mismatch");
//                 EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//                 return 0;
//             }
//         } else {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Cannot open idx for existing seal");
//             enter_degraded_mode("cannot open idx");
//             return 0;
//         }
//     } else {
//         if (try_rehome_stale_seal_to_rotated_file() == 0) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             log_msg("stale seal rehomed; treating as first-run");
//             return 1;
//         }
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("No idx file but existing seal expects one");
//         enter_degraded_mode("no idx but seal exists");
//         return 0;
//     }

//     /* 一切匹配：把 seal 的链和行数装回内存状态（用于继续处理） */
//     unsigned long long N2 = strtoull(S.records, NULL, 10);
//     for (int i=0;i<32;i++){
//         unsigned int hi,lo;
//         if (sscanf(&S.chain[i*2], "%1x%1x", &hi, &lo) != 2) {
//             EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//             trigger_alarm("Existing seal chain hex parse error");
//             enter_degraded_mode("seal chain hex parse error");
//             return 0;
//         }
//         g_chain[i] = (hi<<4)|lo;
//     }
//     g_lines = N2;

//     FILE *ix2 = fopen(IDX_PATH,"r");
//     if (!ix2) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); trigger_alarm("Cannot open idx for reading offset"); enter_degraded_mode("cannot open idx for offset"); return 0; }
//     char b2[1024]; unsigned long long last_off = 0; unsigned long long last_i = 0;
//     while (fgets(b2,sizeof(b2),ix2)) {
//         if (b2[0]=='#') continue;
//         unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
//         if (sscanf(b2,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
//             last_i = i; last_off = off;
//         }
//     }
//     fclose(ix2);
//     if (last_i != g_lines) {
//         EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//         trigger_alarm("Existing idx last_i != seal records - inconsistent");
//         enter_degraded_mode("idx last_i != seal records");
//         return 0;
//     }
//     g_offset = last_off;

//     EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
//     return 0;
// }

// /* 处理一行（从 audit.log 读取的内容） */
// static int gmseal_on_line_from_logbuf(const char *buf, size_t raw_n)
// {
//     if (g_degraded_mode) return 0; /* 降级态不写入 */

//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     if (!sm3) return -1;

//     size_t normlen = raw_n;
//     while (normlen > 0 && (buf[normlen-1]=='\n' || buf[normlen-1]=='\r')) normlen--;

//     unsigned char lhash[32], cand_chain[32];
//     unsigned int lm=0, hm=0;

//     EVP_MD_CTX *c1 = EVP_MD_CTX_new();
//     if (!c1) return -1;
//     if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(c1, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(c1, lhash, &lm) != 1 || lm != 32) {
//         EVP_MD_CTX_free(c1); return -1;
//     }
//     EVP_MD_CTX_free(c1);

//     EVP_MD_CTX *c2 = EVP_MD_CTX_new();
//     if (!c2) return -1;
//     if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
//         EVP_DigestUpdate(c2, g_chain, sizeof(g_chain)) != 1 ||
//         EVP_DigestUpdate(c2, buf, normlen) != 1 ||
//         EVP_DigestFinal_ex(c2, cand_chain, &hm) != 1 || hm != 32) {
//         EVP_MD_CTX_free(c2); return -1;
//     }
//     EVP_MD_CTX_free(c2);

//     char lhex[65], chex[65];
//     hexdump(lhash, 32, lhex, sizeof(lhex));
//     hexdump(cand_chain, 32, chex, sizeof(chex));

//     if (g_idx_fd < 0 && open_idx_handle() != 0) {
//         log_msg("gmseal_on_line: open_idx_handle failed");
//         return -1;
//     }

//     /* 兜底“offset 单调”检查，防止重复入库同一偏移的行 */
//     IdxEntry tail = {0};
//     if (read_last_idx_entry(&tail) == 0) {
//         if (g_lines == tail.i && g_offset <= tail.off) {
//             log_msg("gmseal_on_line: non-monotonic offset (g_offset=%llu <= tail.off=%llu), skip duplicate",
//                     (unsigned long long)g_offset, (unsigned long long)tail.off);
//             return 0;
//         }
//         if (tail.i == g_lines && strncmp(tail.chash, chex, sizeof(tail.chash)) == 0) {
//             return 0;
//         }
//     }

//     if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
//         log_msg("gmseal_on_line: append_idx_line_atomic failed for next i=%llu", (unsigned long long)(g_lines + 1));
//         return -1;
//     }

//     memcpy(g_chain, cand_chain, 32);
//     g_lines++;
//     g_offset += (unsigned long long)raw_n;
//     return 0;
// }

// /* 读取 audit.log 的 tail 全部吃完（带运行期前置校验） */
// static int process_all_available_lines(void)
// {
//     if (g_degraded_mode) return 0; /* 降级态下不做读写，保持证据链 */
//     if (!g_log_fp && open_log_handle() != 0) return -1;

//     if (g_lines > 0) {
//         IdxEntry tail = {0};
//         if (read_last_idx_entry(&tail) != 0) {
//             log_msg("preflight: cannot read last idx entry");
//             enter_degraded_mode("Runtime preflight failed: cannot read last idx entry");
//             return -1;
//         }
//         if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) {
//             log_msg("preflight: fseeko to tail.off=%llu failed: %s",
//                     (unsigned long long)tail.off, strerror(errno));
//             enter_degraded_mode("preflight fseeko failed");
//             return 0;
//         }
//         char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
//         if (n <= 0) {
//             free(line);
//             log_msg("preflight: cannot read line at off=%llu", (unsigned long long)tail.off);
//             enter_degraded_mode("preflight cannot read last line");
//             return 0;
//         }
//         size_t norm = normalize_line(line, (size_t)n);
//         const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//         unsigned char md[32]; unsigned int mdlen = 0;
//         EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//         int ok = ctx && sm3 &&
//                  EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
//                  EVP_DigestUpdate(ctx, line, norm)==1 &&
//                  EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
//         if (ctx) EVP_MD_CTX_free(ctx);
//         char lhex_now[65]={0};
//         if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));

//         if (!ok || strcmp(lhex_now, tail.lhash) != 0) {
//             log_msg("preflight: last processed line content mismatch (idx lhash=%s, now=%s)",
//                     tail.lhash, lhex_now[0]?lhex_now:"<err>");
//             free(line);
//             enter_degraded_mode("preflight mismatch at runtime");
//             return 0;
//         }

//         /* 预检通过后，把 g_offset 前移到“最后一行的结尾”，避免重复入库 */
//         g_offset = tail.off + (unsigned long long)n;
//         free(line);
//     }

//     if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
//         log_msg("process_all_available_lines: fseeko failed: %s", strerror(errno));
//         return -1;
//     }
//     int consumed = 0;
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while ((n = getline(&line, &cap, g_log_fp)) > 0) {
//         if (gmseal_on_line_from_logbuf(line, (size_t)n) != 0) {
//             log_msg("process_all_available_lines: on_line failed");
//             free(line); return -1;
//         }
//         consumed++;
//     }
//     free(line);
//     return consumed;
// }

// /* 写 seal 前做一次“只读预检”，不做任何 rebuild/截短 */
// static int safe_pre_seal_check(void)
// {
//     if (g_degraded_mode) return -1;
//     if (g_lines == 0) return 0;
//     if (!g_log_fp && open_log_handle() != 0) return -1;

//     IdxEntry tail = {0};
//     if (read_last_idx_entry(&tail) != 0) return -1;

//     if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) return -1;

//     char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
//     if (n <= 0) { free(line); return -1; }
//     size_t norm = normalize_line(line,(size_t)n);
//     const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
//     unsigned char md[32]; unsigned int mdlen=0;
//     EVP_MD_CTX *ctx = EVP_MD_CTX_new();
//     int ok = ctx && sm3 &&
//              EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
//              EVP_DigestUpdate(ctx, line, norm)==1 &&
//              EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
//     if (ctx) EVP_MD_CTX_free(ctx);
//     char lhex_now[65]={0};
//     if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));
//     free(line);
//     if (!ok || strcmp(lhex_now, tail.lhash) != 0) return -1;
//     return 0;
// }

// /* 写 seal（安全版：不做 rebuild，只做预检） */
// static int gmseal_write_seal(void)
// {
//     if (g_degraded_mode) {
//         log_msg("In degraded mode: skip writing seal");
//         return 0;
//     }
//     if (safe_pre_seal_check() != 0) {
//         log_msg("safe_pre_seal_check failed; entering degraded mode");
//         enter_degraded_mode("pre-seal check failed");
//         return -1;
//     }
//     return gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
// }

// /* 初始化 */
// static int gmseal_init(void)
// {
//     memset(g_chain,0,sizeof(g_chain));
//     g_lines = 0; g_offset = 0; g_degraded_mode = 0;

//     (void)open_idx_handle();
//     (void)open_log_handle();

//     int vs = verify_existing_seal();
//     if (vs < 0) { log_msg("Existing seal verify error - aborting"); return -1; }

//     if (vs > 0) {
//         /* 首次/归位后：仅当 idx 为空时才允许“全量扫描建立 idx” */
//         unsigned long long idx_lines = idx_count_lines();
//         if (idx_lines == 0) {
//             unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
//             if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
//                 log_msg("rebuild_idx_from_log_atomic failed (first run)"); return -1;
//             }
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed after first-run rebuild"); return -1;
//             }
//             log_msg("first-run: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         } else {
//             /* 已有 idx（例如上次宕机遗留），直接从 idx 尾恢复 */
//             if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//                 log_msg("recover_tail_from_idx failed (existing idx)"); return -1;
//             }
//             log_msg("resume from existing idx: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
//         }
//     } else {
//         /* seal 已验证通过或进入降级：严格按 idx 尾恢复，不做任何 rebuild 或截短 */
//         if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
//             log_msg("recover_tail_from_idx failed"); return -1;
//         }
//         log_msg("verified or degraded: lines=%llu off=%llu (no rebuild)", (unsigned long long)g_lines, (unsigned long long)g_offset);
//     }

//     (void)check_rotation_and_rollover();
//     return 0;
// }

// /* stdin 驱动 */
// static int stdin_event_loop(void)
// {
//     char *line = NULL; size_t cap = 0; ssize_t n;
//     while (g_run && (n = getline(&line, &cap, stdin)) != -1) {
//         (void)n;

//         (void)check_rotation_and_rollover();

//         int rc = process_all_available_lines();
//         if (rc < 0) {
//             trigger_alarm("gmseal plugin processing error - exiting");
//             free(line); return -1;
//         }
//         if (!g_degraded_mode && ALWAYS_REFRESH_SEAL && rc > 0) {
//             if (gmseal_write_seal() != 0) log_msg("gmseal_write_seal failed");
//         }
//     }
//     if (line) free(line);
//     return 0;
// }

// /* main */
// int main(int argc, char **argv)
// {
//     OpenSSL_add_all_algorithms();
//     ERR_load_crypto_strings();

//     log_open();
//     log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

//     if (acquire_singleton_lock() != 0) {
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 0;
//     }

//     if (gm_crypto_init() != 0) {
//         log_msg("gm_crypto_init failed");
//         fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
//         release_singleton_lock();
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 2;
//     }

//     signal(SIGINT, on_signal);
//     signal(SIGTERM, on_signal);

//     if (gmseal_init() != 0) {
//         log_msg("gmseal_init failed (abort)");
//         gm_crypto_cleanup();
//         release_singleton_lock();
//         if (g_log) fclose(g_log);
//         EVP_cleanup();
//         ERR_free_strings();
//         return 2;
//     }

//     /* 启动后先把当前可读尾部吃完，再写首次 seal（仅预检） */
//     {
//         int ate = process_all_available_lines();
//         if (ate < 0) {
//             log_msg("initial tail scan failed");
//         }
//     }
//     if (!g_degraded_mode) {
//         if (gmseal_write_seal() != 0) {
//             log_msg("initial seal write failed");
//         }
//     } else {
//         log_msg("initial: degraded mode active, skip initial seal");
//     }

//     if (stdin_event_loop() != 0) {
//         log_msg("stdin_event_loop aborted due to errors");
//     }

//     (void)gmseal_write_seal(); /* 降级态里会自动跳过 */
//     (void)check_rotation_and_rollover();

//     close_idx_handle();
//     close_log_handle();
//     gm_crypto_cleanup();
//     release_singleton_lock();
//     if (g_log) fclose(g_log);
//     EVP_cleanup();
//     ERR_free_strings();
//     log_msg("audisp-gmseal exited");
//     return 0;
// }


//版本---可疑重写=快照旧文件+归档 idx/seal + 立即切换新代，不再盲目降级）
//解决再次触发审计日志后，未能报出之前被篡改行
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/time.h>
#include <dirent.h>
#include <sys/sysmacros.h>

#include <openssl/evp.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/err.h>

#include "../audit-gm/alarm.c"
#include "../../lib/gm_crypto.h"

#define LOG_PATH "/var/log/audit/audit.log"
#define IDX_PATH "/var/log/audit/audit.log.idx"
#define SEAL_PATH "/var/log/audit/audit.log.seal"

/* 证书/口令派生 */
#define P12_PATH "/etc/pki/audit/sm2.p12"
#define SALT_PATH "/etc/pki/audit/p12_salt"
#define PASS_PATH "/etc/pki/audit/p12_pass"
#define SM2_ID "1234567812345678"
#define SALT_BYTES 16
#define DERIVE_BYTES 32
#define DERIVE_ITERATIONS 200000

/* 每行写 seal（测试保持1，生产可按需调节） */
#define ALWAYS_REFRESH_SEAL 1

/* 插件自身日志 */
#define GMLOG_DIR "/var/log/audit-gm"
#define GMLOG_FILE GMLOG_DIR "/audisp-gmseal.log"

/* 单例锁 */
#define RUN_LOCK "/var/run/audisp-gmseal.lock"

static FILE *g_log = NULL;
static int g_run_lock_fd = -1;

static void log_open(void)
{
    mkdir(GMLOG_DIR, 0755);
    g_log = fopen(GMLOG_FILE, "a");
}
static void log_msg(const char *fmt, ...)
{
    if (!g_log) return;
    va_list ap;
    va_start(ap, fmt);
    time_t t = time(NULL);
    char tb[64];
    strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S", localtime(&t));
    fprintf(g_log, "[%s] ", tb);
    vfprintf(g_log, fmt, ap);
    fprintf(g_log, "\n");
    fflush(g_log);
    va_end(ap);
}
static int acquire_singleton_lock(void)
{
    g_run_lock_fd = open(RUN_LOCK, O_CREAT|O_RDWR, 0644);
    if (g_run_lock_fd < 0) {
        log_msg("open run lock %s failed: %s", RUN_LOCK, strerror(errno));
        return -1;
    }
    if (flock(g_run_lock_fd, LOCK_EX | LOCK_NB) != 0) {
        log_msg("another gmseal instance is running (lock busy), exiting");
        return -1;
    }
    dprintf(g_run_lock_fd, "%ld\n", (long)getpid());
    return 0;
}
static void release_singleton_lock(void)
{
    if (g_run_lock_fd >= 0) {
        flock(g_run_lock_fd, LOCK_UN);
        close(g_run_lock_fd);
        unlink(RUN_LOCK);
        g_run_lock_fd = -1;
    }
}

static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
{
    static const char hex[] = "0123456789abcdef";
    if (outlen < len * 2 + 1) return;
    for (size_t i = 0; i < len; i++) {
        out[i * 2] = hex[(buf[i] >> 4) & 0xF];
        out[i * 2 + 1] = hex[buf[i] & 0xF];
    }
    out[len * 2] = 0;
}

static char *b64_encode(const unsigned char *buf, size_t len)
{
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO *mem = BIO_new(BIO_s_mem());
    if (!b64 || !mem) { if (b64) BIO_free(b64); if (mem) BIO_free(mem); return NULL; }
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
#endif
    BIO_push(b64, mem);
    if (BIO_write(b64, buf, (int)len) <= 0) { BIO_free_all(b64); return NULL; }
    (void)BIO_flush(b64);
    BUF_MEM *bptr = NULL;
    BIO_get_mem_ptr(b64, &bptr);
    char *out = malloc(bptr->length + 1);
    if (!out) { BIO_free_all(b64); return NULL; }
    memcpy(out, bptr->data, bptr->length);
    out[bptr->length] = 0;
    BIO_free_all(b64);
    return out;
}

static unsigned char *b64_decode(const char *b64, size_t *olen)
{
    BIO *b = BIO_new(BIO_f_base64());
    BIO *m = BIO_new_mem_buf(b64, -1);
    if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
#endif
    BIO_push(b, m);
    size_t cap = strlen(b64) * 3 / 4 + 16;
    unsigned char *buf = malloc(cap);
    int n = BIO_read(b, buf, (int)cap);
    BIO_free_all(b);
    if (n <= 0) { free(buf); return NULL; }
    *olen = (size_t)n;
    return buf;
}

static int sm3_file_hex(const char *path, char out_hex[65])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) { fclose(f); return -1; }
    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    unsigned char buf[4096];
    size_t n;
    while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
        if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    }
    fclose(f);
    unsigned char md[32]; unsigned int mdlen = 0;
    if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
    EVP_MD_CTX_free(ctx);
    hexdump(md, 32, out_hex, 65);
    return 0;
}

static size_t normalize_line(char *buf, size_t len)
{
    while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) len--;
    return len;
}

static unsigned long long count_file_lines_simple(const char *path)
{
    FILE *f = fopen(path, "rb");
    if (!f) return 0;
    char *line = NULL; size_t cap = 0; ssize_t n; unsigned long long ln = 0;
    while ((n = getline(&line, &cap, f)) != -1) ln++;
    free(line); fclose(f); return ln;
}

/* ------------ idx helpers ------------ */
typedef struct {
    unsigned long long i;
    unsigned long long off;
    char lhash[65];
    char chash[65];
} IdxEntry;

typedef struct {
    IdxEntry *entries;
    size_t count;
} IdxArray;

static void free_idx_array(IdxArray *a) {
    if (!a) return;
    free(a->entries);
    a->entries = NULL; a->count = 0;
}

static int load_idx_entries(const char *path, IdxArray *out) {
    memset(out,0,sizeof(*out));
    FILE *f = fopen(path,"r");
    if (!f) return -1;
    char buf[1024];
    IdxEntry *arr = NULL; size_t cnt = 0, capv = 0;
    while (fgets(buf,sizeof(buf),f)) {
        if (buf[0] == '#') continue;
        unsigned long long ii=0, off=0; char lhex[128]={0}, chex[128]={0};
        if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&ii,&off,lhex,chex)==4) {
            if (cnt == capv) {
                size_t nc = capv ? capv*2 : 256;
                IdxEntry *p = realloc(arr, nc * sizeof(IdxEntry));
                if (!p) { free(arr); fclose(f); return -1; }
                arr = p; capv = nc;
            }
            arr[cnt].i = ii; arr[cnt].off = off;
            strncpy(arr[cnt].lhash, lhex, sizeof(arr[cnt].lhash)-1);
            strncpy(arr[cnt].chash, chex, sizeof(arr[cnt].chash)-1);
            cnt++;
        }
    }
    fclose(f);
    out->entries = arr; out->count = cnt;
    return 0;
}

/* -------- seal 解析 -------- */
struct seal {
    char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
};

static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n'||s[n-1]=='\r')) s[--n] = 0; }
static char *trimws(char *s){ while(*s==' '||*s=='\t') s++; size_t n=strlen(s); while(n>0&&(s[n-1]==' '||s[n-1]=='\t'||s[n-1]=='\r'||s[n-1]=='\n')) s[--n]=0; return s;}

static int parse_seal_file(const char *seal_path, struct seal *S)
{
    memset(S,0,sizeof(*S));
    FILE *f = fopen(seal_path,"r");
    if (!f) return -1;
    char *line = NULL; size_t cap=0; ssize_t n;
    while ((n = getline(&line,&cap,f)) != -1) {
        if (line[0]=='#' || line[0]==0) continue;
        trimnl(line);
        char *eq = strchr(line,'=');
        if (!eq) continue;
        *eq = 0;
        char *k = trimws(line);
        char *v = trimws(eq+1);
        if (strcmp(k,"version")==0) S->version=strdup(v);
        else if (strcmp(k,"hash")==0) S->hash=strdup(v);
        else if (strcmp(k,"sign")==0) S->sign=strdup(v);
        else if (strcmp(k,"sm2_id")==0) S->sm2_id=strdup(v);
        else if (strcmp(k,"path")==0) S->path=strdup(v);
        else if (strcmp(k,"records")==0) S->records=strdup(v);
        else if (strcmp(k,"chain")==0) S->chain=strdup(v);
        else if (strcmp(k,"key_fpr")==0) S->key_fpr=strdup(v);
        else if (strcmp(k,"cert_der_base64")==0) S->cert_b64=strdup(v);
        else if (strcmp(k,"sig_base64")==0) S->sig_b64=strdup(v);
        else if (strcmp(k,"index_sm3")==0) S->index_sm3=strdup(v);
    }
    free(line); fclose(f);
    if (S->version && S->hash && S->sign && S->sm2_id && S->path &&
        S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3) return 0;
    return -1;
}
static void free_seal(struct seal *S)
{
    free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
    free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
    free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
}
static char *build_canon_from_seal(struct seal *S)
{
    char *buf = NULL;
    if (asprintf(&buf,
        "version=%s\n"
        "hash=%s\n"
        "sign=%s\n"
        "sm2_id=%s\n"
        "path=%s\n"
        "records=%s\n"
        "chain=%s\n"
        "key_fpr=%s\n"
        "index_sm3=%s\n",
        S->version,S->hash,S->sign,S->sm2_id,S->path,S->records,S->chain,S->key_fpr,S->index_sm3) < 0) return NULL;
    return buf;
}

/* ---------- 简易 SM3 链（限制行数） ---------- */
static int sm3_chain_file_limit_local(const char *path, unsigned long long limit_lines, unsigned char out32[32])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    unsigned char H[32] = {0};
    unsigned long long scanned = 0;
    FILE *fp = fopen(path,"rb");
    if (!fp) return -1;
    char *line = NULL; size_t cap=0; ssize_t n;
    while (scanned < limit_lines && (n = getline(&line,&cap,fp)) != -1) {
        size_t normlen = normalize_line(line,(size_t)n);
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!ctx) { fclose(fp); free(line); return -1; }
        int ok = (EVP_DigestInit_ex(ctx, sm3, NULL) == 1 &&
                  EVP_DigestUpdate(ctx, H, sizeof(H)) == 1 &&
                  EVP_DigestUpdate(ctx, line, normlen) == 1);
        unsigned int mdlen = 0;
        if (!ok || EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        EVP_MD_CTX_free(ctx);
        scanned++;
    }
    free(line); fclose(fp); memcpy(out32,H,32); return 0;
}

/* ---------- idx handle ---------- */
static int g_idx_fd = -1;
static FILE *g_idx = NULL;

static void close_idx_handle(void) {
    if (g_idx) { fclose(g_idx); g_idx = NULL; }
    if (g_idx_fd >= 0) { close(g_idx_fd); g_idx_fd = -1; }
}
static int open_idx_handle(void) {
    if (g_idx_fd >= 0) return 0;
    int fd = open(IDX_PATH, O_RDWR | O_CREAT | O_APPEND, 0600);
    if (fd < 0) {
        log_msg("open_idx_handle: open %s failed: %s", IDX_PATH, strerror(errno));
        return -1;
    }
    FILE *f = fdopen(dup(fd), "a+");
    if (!f) { log_msg("open_idx_handle: fdopen failed: %s", strerror(errno)); close(fd); return -1; }
    g_idx_fd = fd;
    g_idx = f;
    fchmod(g_idx_fd, 0600);
    return 0;
}

static int g_degraded_mode = 0;

static int append_idx_line_atomic(unsigned long long i, unsigned long long off, const char *lhex, const char *chex) {
    if (g_degraded_mode) {
        log_msg("append_idx_line_atomic: degraded mode, skip append");
        return 0;
    }
    if (g_idx_fd < 0) {
        log_msg("append_idx_line_atomic: idx fd not open");
        return -1;
    }
    if (flock(g_idx_fd, LOCK_EX) != 0) {
        log_msg("append_idx_line_atomic: flock EX failed: %s", strerror(errno));
    }
    int ret = 0;
    ssize_t w = dprintf(g_idx_fd, "i=%llu off=%llu lhash=%s chash=%s\n",
                        (unsigned long long)i, (unsigned long long)off, lhex, chex);
    if (w < 0) { log_msg("append_idx_line_atomic: dprintf failed: %s", strerror(errno)); ret = -1; }
    else fsync(g_idx_fd);
    flock(g_idx_fd, LOCK_UN);
    return ret;
}

/* *** 全量重建：仅在 idx 为空时使用 *** */
static int rebuild_idx_from_log_atomic(const char *logpath, const char *idxpath,
                                       unsigned char out_chain[32],
                                       unsigned long long *out_lines,
                                       unsigned long long *out_offset)
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    FILE *lf = fopen(logpath,"rb");
    if (!lf) return -1;
    char *line = NULL; size_t cap=0; ssize_t n;
    unsigned long long ln=0, offset=0;
    unsigned char H[32]; memset(H,0,sizeof(H));

    close_idx_handle();

    char tmppath[PATH_MAX];
    snprintf(tmppath,sizeof(tmppath), "%s.tmp", idxpath);
    FILE *tf = fopen(tmppath, "w");
    if (!tf) { fclose(lf); return -1; }
    chmod(tmppath, 0600);

    while ((n = getline(&line,&cap,lf)) != -1) {
        ln++;
        size_t normlen = normalize_line(line,(size_t)n);
        EVP_MD_CTX *c1 = EVP_MD_CTX_new(); unsigned char L[32]; unsigned int lm=0;
        if (!c1 || EVP_DigestInit_ex(c1, sm3, NULL)!=1 || EVP_DigestUpdate(c1, line, normlen)!=1 || EVP_DigestFinal_ex(c1,L,&lm)!=1 || lm!=32) {
            if (c1) EVP_MD_CTX_free(c1);
            fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
        }
        EVP_MD_CTX_free(c1);

        EVP_MD_CTX *c2 = EVP_MD_CTX_new(); unsigned char newH[32]; unsigned int hm=0;
        if (!c2 || EVP_DigestInit_ex(c2, sm3, NULL)!=1 || EVP_DigestUpdate(c2, H, sizeof(H))!=1 || EVP_DigestUpdate(c2, line, normlen)!=1 || EVP_DigestFinal_ex(c2, newH, &hm)!=1 || hm!=32) {
            if (c2) EVP_MD_CTX_free(c2);
            fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
        }
        EVP_MD_CTX_free(c2);
        memcpy(H, newH, 32);

        char lhex[65], chex[65];
        hexdump(L,32,lhex,sizeof(lhex));
        hexdump(H,32,chex,sizeof(chex));
        if (fprintf(tf, "i=%llu off=%llu lhash=%s chash=%s\n",
                    (unsigned long long)ln, (unsigned long long)offset, lhex, chex) < 0) {
            fclose(lf); fclose(tf); unlink(tmppath); free(line); return -1;
        }
        offset += (unsigned long long)n;
    }
    free(line); fclose(lf);
    fflush(tf); fsync(fileno(tf)); fclose(tf);
    if (rename(tmppath, idxpath) != 0) { unlink(tmppath); return -1; }
    chmod(idxpath, 0600);
    if (open_idx_handle() != 0) { log_msg("rebuild: open_idx_handle failed after rename"); return -1; }
    if (out_chain) memcpy(out_chain,H,32);
    if (out_lines) *out_lines = ln;
    if (out_offset) *out_offset = offset;
    return 0;
}

static int truncate_idx_to_i_atomic(const char *idxpath, unsigned long long keep_i)
{
    if (g_degraded_mode) {
        log_msg("truncate_idx_to_i_atomic: degraded mode, skip truncation");
        return 0;
    }
    close_idx_handle();
    FILE *in = fopen(idxpath,"r");
    if (!in) {
        if (keep_i == 0) {
            FILE *out = fopen(idxpath,"w");
            if (!out) return -1;
            fclose(out);
            chmod(idxpath, 0600);
            if (open_idx_handle() != 0) return -1;
            return 0;
        }
        return -1;
    }
    char tmp[PATH_MAX];
    snprintf(tmp,sizeof(tmp), "%s.trunc", idxpath);
    FILE *out = fopen(tmp, "w");
    if (!out) { fclose(in); return -1; }
    char buf[2048];
    while (fgets(buf,sizeof(buf),in)) {
        if (buf[0]=='#') { if (fputs(buf,out) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; } continue; }
        unsigned long long i=0, off=0; char lhex[256]={0}, chex[256]={0};
        if (sscanf(buf,"i=%llu off=%llu lhash=%255s chash=%255s",&i,&off,lhex,chex)==4) {
            if (i <= keep_i) {
                if (fprintf(out,"i=%llu off=%llu lhash=%s chash=%s\n",i,off,lhex,chex) < 0) { fclose(in); fclose(out); unlink(tmp); return -1; }
            } else break;
        }
    }
    fclose(in);
    fflush(out); fsync(fileno(out)); fclose(out);
    if (rename(tmp, idxpath) != 0) { unlink(tmp); return -1; }
    chmod(idxpath, 0600);
    if (open_idx_handle() != 0) { log_msg("truncate: open_idx_handle failed after rename"); return -1; }
    return 0;
}

/* ---------- 全局状态 ---------- */
static unsigned char g_chain[32];
static unsigned long long g_lines = 0;
static unsigned long long g_offset = 0;
static int g_run = 1;

/* 持有当前 log 的 FD/FP 与 inode（用于轮转/可疑重写检测） */
static int   g_log_fd  = -1;
static FILE *g_log_fp  = NULL;
static dev_t g_log_dev = 0;
static ino_t g_log_ino = 0;

static void on_signal(int sig) { if (sig == SIGINT || sig == SIGTERM) g_run = 0; }

/* 进入降级（只读、报警、不再写 idx/seal） */
static void enter_degraded_mode(const char *why)
{
    if (!g_degraded_mode) {
        g_degraded_mode = 1;
        log_msg("ENTER DEGRADED MODE: %s", why ? why : "unknown");
        // trigger_alarm("gmseal degraded mode: %s", why ? why : "unknown");
    }
}

/* 打开/关闭当前 log 句柄 */
static int open_log_handle(void)
{
    if (g_log_fd >= 0) return 0;
    g_log_fd = open(LOG_PATH, O_RDONLY | O_CLOEXEC);
    if (g_log_fd < 0) {
        log_msg("open_log_handle: open %s failed: %s", LOG_PATH, strerror(errno));
        return -1;
    }
    struct stat st;
    if (fstat(g_log_fd, &st) != 0) {
        log_msg("open_log_handle: fstat failed: %s", strerror(errno));
        close(g_log_fd); g_log_fd = -1;
        return -1;
    }
    g_log_dev = st.st_dev;
    g_log_ino = st.st_ino;
    g_log_fp = fdopen(dup(g_log_fd), "rb");
    if (!g_log_fp) {
        log_msg("open_log_handle: fdopen failed: %s", strerror(errno));
        close(g_log_fd); g_log_fd = -1;
        return -1;
    }
    return 0;
}
static void close_log_handle(void)
{
    if (g_log_fp) { fclose(g_log_fp); g_log_fp = NULL; }
    if (g_log_fd >= 0) { close(g_log_fd); g_log_fd = -1; }
    g_log_dev = 0; g_log_ino = 0;
}

/* 从 idx 尾恢复 */
static int recover_tail_from_idx(unsigned long long *out_i,
                                 unsigned long long *out_off,
                                 unsigned char out_chain[32])
{
    FILE *ix = fopen(IDX_PATH, "r");
    if (!ix) return -1;
    char buf[1024]; unsigned long long last_i = 0, last_off = 0;
    char last_ch[65] = {0};
    while (fgets(buf, sizeof(buf), ix)) {
        if (buf[0]=='#') continue;
        unsigned long long i=0, off=0; char l[128]={0}, c[128]={0};
        if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) {
            last_i = i; last_off = off; strncpy(last_ch, c, sizeof(last_ch)-1);
        }
    }
    fclose(ix);
    if (out_i) *out_i = last_i;
    if (out_off) *out_off = last_off;
    if (out_chain) {
        if (last_i == 0) { memset(out_chain,0,32); return 0; }
        for (int i=0;i<32;i++){
            unsigned int hi,lo;
            if (sscanf(&last_ch[i*2], "%1x%1x", &hi,&lo) != 2) return -1;
            out_chain[i]=(hi<<4)|lo;
        }
    }
    return 0;
}

static int read_last_idx_entry(IdxEntry *out)
{
    if (!out) return -1;
    memset(out,0,sizeof(*out));
    FILE *ix = fopen(IDX_PATH,"r");
    if (!ix) return -1;
    char buf[1024];
    int have = 0;
    while (fgets(buf,sizeof(buf),ix)) {
        if (buf[0]=='#') continue;
        unsigned long long i=0, off=0; char lhex[128]={0}, chex[128]={0};
        if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,lhex,chex)==4) {
            out->i = i; out->off = off;
            strncpy(out->lhash, lhex, sizeof(out->lhash)-1);
            strncpy(out->chash, chex, sizeof(out->chash)-1);
            have = 1;
        }
    }
    fclose(ix);
    return have ? 0 : -1;
}

static unsigned long long idx_count_lines(void)
{
    FILE *f = fopen(IDX_PATH, "r");
    if (!f) return 0;
    char buf[256];
    unsigned long long cnt = 0;
    while (fgets(buf,sizeof(buf),f)) {
        if (buf[0] == '#') continue;
        unsigned long long i=0, off=0; char l[128], c[128];
        if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i,&off,l,c)==4) cnt++;
    }
    fclose(f);
    return cnt;
}

/* 读取旧 FD 到 EOF，刷入 idx */
static int drain_old_file_to_eof(void)
{
    if (!g_log_fp) return 0;
    if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
        log_msg("drain_old_file_to_eof: fseeko failed: %s", strerror(errno));
        return -1;
    }
    char *line = NULL; size_t cap = 0; ssize_t n;
    int cnt = 0;
    while ((n = getline(&line, &cap, g_log_fp)) > 0) {
        const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
        if (!sm3) { free(line); return -1; }
        size_t normlen = normalize_line(line,(size_t)n);

        unsigned char lhash[32], cand_chain[32];
        unsigned int lm=0, hm=0;

        EVP_MD_CTX *c1 = EVP_MD_CTX_new();
        if (!c1) { free(line); return -1; }
        if (EVP_DigestInit_ex(c1, sm3, NULL)!=1 ||
            EVP_DigestUpdate(c1, line, normlen)!=1 ||
            EVP_DigestFinal_ex(c1, lhash, &lm)!=1 || lm!=32) {
            EVP_MD_CTX_free(c1); free(line); return -1;
        }
        EVP_MD_CTX_free(c1);

        EVP_MD_CTX *c2 = EVP_MD_CTX_new();
        if (!c2) { free(line); return -1; }
        if (EVP_DigestInit_ex(c2, sm3, NULL)!=1 ||
            EVP_DigestUpdate(c2, g_chain, sizeof(g_chain))!=1 ||
            EVP_DigestUpdate(c2, line, normlen)!=1 ||
            EVP_DigestFinal_ex(c2, cand_chain, &hm)!=1 || hm!=32) {
            EVP_MD_CTX_free(c2); free(line); return -1;
        }
        EVP_MD_CTX_free(c2);

        char lhex[65], chex[65];
        hexdump(lhash,32,lhex,sizeof(lhex));
        hexdump(cand_chain,32,chex,sizeof(chex));

        if (g_idx_fd < 0 && open_idx_handle() != 0) {
            log_msg("drain_old_file_to_eof: open_idx_handle failed");
            free(line); return -1;
        }
        if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
            log_msg("drain_old_file_to_eof: append idx failed");
            free(line); return -1;
        }
        memcpy(g_chain, cand_chain, 32);
        g_lines++;
        g_offset += (unsigned long long)n;
        cnt++;
    }
    free(line);
    return cnt;
}

/* ---- 写 seal（通用） ---- */
static int gmseal_write_seal_for(const char *log_path_for_canon,
                                 const char *idx_path_for_hash,
                                 const char *seal_out_path)
{
    if (g_degraded_mode) {
        log_msg("In degraded mode: skip writing seal_for(%s)", log_path_for_canon);
        return 0;
    }

    EVP_PKEY *pkey = NULL; X509 *cert = NULL;

    FILE *pf = fopen(PASS_PATH,"r");
    if (!pf) { log_msg("open PASS_PATH failed"); return -1; }
    char *secret = NULL; size_t cap=0; ssize_t n = getline(&secret, &cap, pf); fclose(pf);
    if (n <= 0) { free(secret); log_msg("read PASS_PATH empty"); return -1; }
    while (n && (secret[n-1]=='\n'||secret[n-1]=='\r')) secret[--n]=0;

    unsigned char salt[SALT_BYTES];
    FILE *sf = fopen(SALT_PATH,"r");
    if (!sf) { log_msg("open SALT_PATH failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
    char saltbuf[256]; if (!fgets(saltbuf,sizeof(saltbuf),sf)) { fclose(sf); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
    fclose(sf);
    size_t slen = strlen(saltbuf); while (slen && (saltbuf[slen-1]=='\n'||saltbuf[slen-1]=='\r')) saltbuf[--slen]=0;
    if (slen != (size_t)SALT_BYTES*2) { log_msg("salt length error"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; }
    for (size_t i=0;i<SALT_BYTES;i++){ unsigned int hi,lo; if (sscanf(&saltbuf[i*2], "%1x%1x", &hi,&lo) != 2) { OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1; } salt[i] = (hi<<4)|lo; }

    unsigned char derived[DERIVE_BYTES];
    if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, SALT_BYTES, DERIVE_ITERATIONS, EVP_get_digestbyname("sm3"), DERIVE_BYTES, derived)) {
        log_msg("PBKDF2-SM3 failed"); OPENSSL_cleanse(secret, strlen(secret)); free(secret); return -1;
    }
    char passhex[DERIVE_BYTES*2+1]; hexdump(derived, DERIVE_BYTES, passhex, sizeof(passhex));
    OPENSSL_cleanse(secret, strlen(secret)); free(secret);

    FILE *pf2 = fopen(P12_PATH,"rb");
    if (!pf2) { log_msg("open P12_PATH failed"); return -1; }
    PKCS12 *p12 = d2i_PKCS12_fp(pf2, NULL); fclose(pf2);
    if (!p12) { log_msg("d2i_PKCS12_fp failed"); return -1; }
    if (!PKCS12_parse(p12, passhex, &pkey, &cert, NULL)) { log_msg("PKCS12_parse failed"); PKCS12_free(p12); return -1; }
    PKCS12_free(p12);

    unsigned char *der=NULL; int derlen = i2d_X509(cert, &der);
    if (derlen <= 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }
    EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdlen=0;
    if (!ctx || EVP_DigestInit_ex(ctx, EVP_get_digestbyname("sm3"), NULL) != 1 || EVP_DigestUpdate(ctx, der, derlen) != 1 || EVP_DigestFinal_ex(ctx, mdv, &mdlen) != 1) {
        if (ctx) EVP_MD_CTX_free(ctx); OPENSSL_free(der); EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }
    EVP_MD_CTX_free(ctx); OPENSSL_free(der);
    char fpr_hex[65]; hexdump(mdv, mdlen, fpr_hex, sizeof(fpr_hex));

    char chain_hex[65]; hexdump(g_chain, 32, chain_hex, sizeof(chain_hex));

    char idx_sm3[65] = {0};
    if (sm3_file_hex(idx_path_for_hash, idx_sm3) != 0) { EVP_PKEY_free(pkey); X509_free(cert); log_msg("sm3 idx failed for %s", idx_path_for_hash); return -1; }

    char *canon = NULL;
    if (asprintf(&canon, "version=1\nhash=sm3\nsign=sm2\nsm2_id=%s\npath=%s\nrecords=%llu\nchain=%s\nkey_fpr=%s\nindex_sm3=%s\n",
        SM2_ID, log_path_for_canon, (unsigned long long)g_lines, chain_hex, fpr_hex, idx_sm3) < 0) { EVP_PKEY_free(pkey); X509_free(cert); return -1; }

    unsigned char *sig=NULL; size_t siglen = 0;
    if (gm_sm2_sign(pkey, (const unsigned char *)canon, strlen(canon), SM2_ID, &sig, &siglen) != 0) {
        log_msg("gm_sm2_sign failed"); free(canon); EVP_PKEY_free(pkey); X509_free(cert); return -1;
    }
    char *sigb64 = b64_encode(sig, siglen);
    OPENSSL_free(sig);

    unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
    char *certb64 = NULL;
    if (der2len > 0) { certb64 = b64_encode(der2, der2len); OPENSSL_free(der2); } else { log_msg("i2d_X509 failed"); free(canon); free(sigb64); EVP_PKEY_free(pkey); X509_free(cert); return -1; }

    EVP_PKEY_free(pkey); X509_free(cert);

    char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp), "%s.tmp", seal_out_path);
    FILE *sfw = fopen(tmp, "w");
    if (!sfw) { log_msg("open seal tmp failed: %s", strerror(errno)); free(canon); free(sigb64); free(certb64); return -1; }
    fprintf(sfw, "# audit-seal v1 (SM2/SM3)\n%s", canon);
    fprintf(sfw, "cert_der_base64=%s\n", certb64);
    fprintf(sfw, "sig_base64=%s\n", sigb64);
    fclose(sfw);
    chmod(tmp, 0600);
    if (rename(tmp, seal_out_path) != 0) { log_msg("rename seal tmp -> %s failed: %s", seal_out_path, strerror(errno)); unlink(tmp); free(canon); free(sigb64); free(certb64); return -1; }
    free(canon); free(sigb64); free(certb64);

    log_msg("seal written: %s (records=%llu path=%s)", seal_out_path, (unsigned long long)g_lines, log_path_for_canon);
    return 0;
}

/* ---------- 小工具：copy/snapshot ---------- */

/* NEW: 简单文件拷贝（路径->路径） */
static int copy_file_path_to_path(const char *src, const char *dst)
{
    int s = open(src, O_RDONLY);
    if (s < 0) return -1;
    int d = open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if (d < 0) { close(s); return -1; }
    char buf[1<<16];
    for (;;) {
        ssize_t n = read(s, buf, sizeof(buf));
        if (n == 0) break;
        if (n < 0) { if (errno==EINTR) continue; close(s); close(d); return -1; }
        char *p = buf; ssize_t m = n;
        while (m > 0) {
            ssize_t w = write(d, p, m);
            if (w < 0) { if (errno==EINTR) continue; close(s); close(d); return -1; }
            p += w; m -= w;
        }
    }
    fsync(d);
    close(s); close(d);
    return 0;
}

/* NEW: 把“当前打开的旧 FD”内容快照到文件（完整拷贝） */
static int snapshot_current_fd_to_path(int fd, const char *dst)
{
    int d = open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0600);
    if (d < 0) return -1;
    if (lseek(fd, 0, SEEK_SET) < 0) { close(d); return -1; }
    char buf[1<<16];
    for (;;) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n == 0) break;
        if (n < 0) { if (errno==EINTR) continue; close(d); return -1; }
        char *p = buf; ssize_t m = n;
        while (m > 0) {
            ssize_t w = write(d, p, m);
            if (w < 0) { if (errno==EINTR) continue; close(d); return -1; }
            p += w; m -= w;
        }
    }
    fsync(d);
    close(d);
    return 0;
}

/* NEW: 归档 idx/seal 到某个日志文件名（用于合法轮转或旧 FD 快照） */
static int archive_current_idx_and_seal_to(const char *archived_log_path)
{
    char idx_dst[PATH_MAX], seal_dst[PATH_MAX];
    snprintf(idx_dst,  sizeof(idx_dst),  "%s.idx",  archived_log_path);
    snprintf(seal_dst, sizeof(seal_dst), "%s.seal", archived_log_path);

    close_idx_handle();

    if (rename(IDX_PATH, idx_dst) != 0) {
        log_msg("archive: rename %s -> %s failed: %s", IDX_PATH, idx_dst, strerror(errno));
        return -1;
    }
    chmod(idx_dst, 0600);

    if (gmseal_write_seal_for(archived_log_path, idx_dst, seal_dst) != 0) {
        log_msg("archive: write seal_for(%s) failed", archived_log_path);
        return -1;
    }
    return 0;
}

/* 用 inode 在 /var/log/audit 查找已轮转文件名 */
static int find_rotated_name_by_inode(char out_path[PATH_MAX], dev_t dev, ino_t ino)
{
    DIR *d = opendir("/var/log/audit");
    if (!d) return -1;
    struct dirent *de;
    struct stat st;
    while ((de = readdir(d))) {
        if (strncmp(de->d_name, "audit.log", 9) != 0) continue;
        char cand[PATH_MAX];
        snprintf(cand, sizeof(cand), "/var/log/audit/%s", de->d_name);
        if (stat(cand, &st) != 0) continue;
        if (st.st_dev == dev && st.st_ino == ino) {
            strncpy(out_path, cand, PATH_MAX-1);
            out_path[PATH_MAX-1] = 0;
            closedir(d);
            return 0;
        }
    }
    closedir(d);
    return -1;
}

/* ---------- 运行期主逻辑：轮转/可疑重写处理 ---------- */
static int check_rotation_and_rollover(void)
{
    if (g_log_fd < 0) return open_log_handle();

    struct stat st_now;
    if (stat(LOG_PATH, &st_now) != 0) {
        return 0;
    }
    if (st_now.st_dev == g_log_dev && st_now.st_ino == g_log_ino) return 0;

    /* inode 变了 -> 先把旧 FD 的尾巴刷进 idx */
    log_msg("detected inode change: old ino=%ju -> new ino=%ju",
            (uintmax_t)g_log_ino, (uintmax_t)st_now.st_ino);

    (void)drain_old_file_to_eof();

    /* 先尝试判断“合法轮转” */
    char rotated_path[PATH_MAX]={0};
    if (find_rotated_name_by_inode(rotated_path, g_log_dev, g_log_ino) == 0) {
        /* 合法轮转：旧文件名找到 */
        log_msg("rotation confirmed: archived file = %s", rotated_path);

        /* 归档当前 idx/seal 到旧名 */
        if (archive_current_idx_and_seal_to(rotated_path) != 0) {
            log_msg("archive_current_idx_and_seal_to failed for %s", rotated_path);
            /* 即便失败，仍尝试切换新代 */
        }

        /* 切换到新代 */
        close_log_handle();
        if (open_log_handle() != 0) {
            log_msg("rotation: open new audit.log failed");
            return -1;
        }
        if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
            log_msg("rotation: truncate new idx failed");
            return -1;
        }
        memset(g_chain,0,sizeof(g_chain));
        g_lines = 0;
        g_offset = 0;

        /* 写空链 seal 作为新代起点（可选） */
        (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
        return 0;
    }

    /* 可疑重写：路径内容被替换但旧 FD 仍在写 */
    log_msg("inode changed but no archived filename found -> suspicious rewrite");

    time_t ts = time(NULL);
    char tampered_path[PATH_MAX], tampered_idx[PATH_MAX], tampered_seal[PATH_MAX];
    char suspected_path[PATH_MAX], suspected_idx[PATH_MAX], suspected_seal[PATH_MAX];

    /* NEW: 1) 取证快照：拷贝“被篡改的路径文件” */
    snprintf(tampered_path, sizeof(tampered_path), "%s.tampered-%ju-%ld", LOG_PATH, (uintmax_t)st_now.st_ino, (long)ts);
    snprintf(tampered_idx,  sizeof(tampered_idx),  "%s.idx",  tampered_path);
    snprintf(tampered_seal, sizeof(tampered_seal), "%s.seal", tampered_path);
    if (copy_file_path_to_path(LOG_PATH, tampered_path) == 0) {
        /* 把当前 idx 复制一份到 tampered.idx */
        if (copy_file_path_to_path(IDX_PATH, tampered_idx) == 0) {
            /* 用“旧一代链值/记录数”对 tampered_path 写 seal（验证应 FAIL，并能列出坏行） */
            if (gmseal_write_seal_for(tampered_path, tampered_idx, tampered_seal) == 0) {
                log_msg("tamper snapshot: created %s (+ .idx/.seal)", tampered_path);
            } else {
                log_msg("tamper snapshot: write seal failed for %s", tampered_path);
            }
        } else {
            log_msg("tamper snapshot: copy idx to %s failed", tampered_idx);
        }
    } else {
        log_msg("tamper snapshot: copy %s -> %s failed", LOG_PATH, tampered_path);
    }

   //  /* NEW: 2) 保全“旧 FD 正确字节”的完整快照 */
   //  snprintf(suspected_path, sizeof(suspected_path), "%s.suspected-%ju-%ld", LOG_PATH, (uintmax_t)g_log_ino, (long)ts);
   //  snprintf(suspected_idx,  sizeof(suspected_idx),  "%s.idx",  suspected_path);
   //  snprintf(suspected_seal, sizeof(suspected_seal), "%s.seal", suspected_path);
   //  if (snapshot_current_fd_to_path(g_log_fd, suspected_path) == 0) {
   //      log_msg("snapshot: created %s", suspected_path);
   //      /* 把当前 idx/seal 归档到 suspected* 文件旁（rename idx，再为 suspected* 写 seal） */
   //      if (archive_current_idx_and_seal_to(suspected_path) == 0) {
   //          log_msg("snapshot: archived idx/seal to %s", suspected_path);
   //      } else {
   //          log_msg("snapshot: archive idx/seal to %s failed", suspected_path);
   //      }
   //  } else {
   //      log_msg("snapshot: copy from old FD to %s failed", suspected_path);
   //  }

    /* 3) 切换到新一代 */
    close_log_handle();
    (void)open_log_handle();
    if (truncate_idx_to_i_atomic(IDX_PATH, 0) != 0) {
        log_msg("suspicious rewrite: truncate new idx failed");
        return -1;
    }
    memset(g_chain,0,sizeof(g_chain));
    g_lines = 0;
    g_offset = 0;

    /* 4) 写新一代的起始 seal（空链或后续很快刷新） */
    (void)gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);

    log_msg("suspicious rewrite handled: tampered & suspected snapshots written; new generation started");
    return 0;
}

/* ---- 写 seal 前只读预检 ---- */
static int safe_pre_seal_check(void)
{
    if (g_degraded_mode) return -1;
    if (g_lines == 0) return 0;
    if (!g_log_fp && open_log_handle() != 0) return -1;

    IdxEntry tail = {0};
    if (read_last_idx_entry(&tail) != 0) return -1;

    if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) return -1;

    char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
    if (n <= 0) { free(line); return -1; }
    size_t norm = normalize_line(line,(size_t)n);
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    unsigned char md[32]; unsigned int mdlen=0;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    int ok = ctx && sm3 &&
             EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
             EVP_DigestUpdate(ctx, line, norm)==1 &&
             EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
    if (ctx) EVP_MD_CTX_free(ctx);
    char lhex_now[65]={0};
    if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));
    free(line);
    if (!ok || strcmp(lhex_now, tail.lhash) != 0) return -1;
    return 0;
}

static int gmseal_write_seal(void)
{
    if (g_degraded_mode) {
        log_msg("In degraded mode: skip writing seal");
        return 0;
    }
    if (safe_pre_seal_check() != 0) {
        log_msg("safe_pre_seal_check failed; entering degraded mode");
        enter_degraded_mode("pre-seal check failed");
        return -1;
    }
    return gmseal_write_seal_for(LOG_PATH, IDX_PATH, SEAL_PATH);
}

/* 处理一行（从 audit.log 读取的内容） */
static int gmseal_on_line_from_logbuf(const char *buf, size_t raw_n)
{
    if (g_degraded_mode) return 0;

    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;

    size_t normlen = raw_n;
    while (normlen > 0 && (buf[normlen-1]=='\n' || buf[normlen-1]=='\r')) normlen--;

    unsigned char lhash[32], cand_chain[32];
    unsigned int lm=0, hm=0;

    EVP_MD_CTX *c1 = EVP_MD_CTX_new();
    if (!c1) return -1;
    if (EVP_DigestInit_ex(c1, sm3, NULL) != 1 ||
        EVP_DigestUpdate(c1, buf, normlen) != 1 ||
        EVP_DigestFinal_ex(c1, lhash, &lm) != 1 || lm != 32) {
        EVP_MD_CTX_free(c1); return -1;
    }
    EVP_MD_CTX_free(c1);

    EVP_MD_CTX *c2 = EVP_MD_CTX_new();
    if (!c2) return -1;
    if (EVP_DigestInit_ex(c2, sm3, NULL) != 1 ||
        EVP_DigestUpdate(c2, g_chain, sizeof(g_chain)) != 1 ||
        EVP_DigestUpdate(c2, buf, normlen) != 1 ||
        EVP_DigestFinal_ex(c2, cand_chain, &hm) != 1 || hm != 32) {
        EVP_MD_CTX_free(c2); return -1;
    }
    EVP_MD_CTX_free(c2);

    char lhex[65], chex[65];
    hexdump(lhash, 32, lhex, sizeof(lhex));
    hexdump(cand_chain, 32, chex, sizeof(chex));

    if (g_idx_fd < 0 && open_idx_handle() != 0) {
        log_msg("gmseal_on_line: open_idx_handle failed");
        return -1;
    }

    IdxEntry tail = {0};
    if (read_last_idx_entry(&tail) == 0) {
        if (g_lines == tail.i && g_offset <= tail.off) {
            log_msg("gmseal_on_line: non-monotonic offset (g_offset=%llu <= tail.off=%llu), skip duplicate",
                    (unsigned long long)g_offset, (unsigned long long)tail.off);
            return 0;
        }
        if (tail.i == g_lines && strncmp(tail.chash, chex, sizeof(tail.chash)) == 0) {
            return 0;
        }
    }

    if (append_idx_line_atomic(g_lines + 1, g_offset, lhex, chex) != 0) {
        log_msg("gmseal_on_line: append_idx_line_atomic failed for next i=%llu", (unsigned long long)(g_lines + 1));
        return -1;
    }

    memcpy(g_chain, cand_chain, 32);
    g_lines++;
    g_offset += (unsigned long long)raw_n;
    return 0;
}

/* 把可读尾部吃完（带运行期前置校验） */
static int process_all_available_lines(void)
{
    if (g_degraded_mode) return 0;
    if (!g_log_fp && open_log_handle() != 0) return -1;

    if (g_lines > 0) {
        IdxEntry tail = {0};
        if (read_last_idx_entry(&tail) != 0) {
            log_msg("preflight: cannot read last idx entry");
            enter_degraded_mode("Runtime preflight failed: cannot read last idx entry");
            return -1;
        }
        if (fseeko(g_log_fp, (off_t)tail.off, SEEK_SET) != 0) {
            log_msg("preflight: fseeko to tail.off=%llu failed: %s",
                    (unsigned long long)tail.off, strerror(errno));
            enter_degraded_mode("preflight fseeko failed");
            return 0;
        }
        char *line = NULL; size_t cap = 0; ssize_t n = getline(&line, &cap, g_log_fp);
        if (n <= 0) {
            free(line);
            log_msg("preflight: cannot read line at off=%llu", (unsigned long long)tail.off);
            enter_degraded_mode("preflight cannot read last line");
            return 0;
        }
        size_t norm = normalize_line(line, (size_t)n);
        const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
        unsigned char md[32]; unsigned int mdlen = 0;
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        int ok = ctx && sm3 &&
                 EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
                 EVP_DigestUpdate(ctx, line, norm)==1 &&
                 EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
        if (ctx) EVP_MD_CTX_free(ctx);
        char lhex_now[65]={0};
        if (ok) hexdump(md,32,lhex_now,sizeof(lhex_now));

        if (!ok || strcmp(lhex_now, tail.lhash) != 0) {
            log_msg("preflight: last processed line content mismatch (idx lhash=%s, now=%s)",
                    tail.lhash, lhex_now[0]?lhex_now:"<err>");
            free(line);
            enter_degraded_mode("preflight mismatch at runtime");
            return 0;
        }

        g_offset = tail.off + (unsigned long long)n;
        free(line);
    }

    if (fseeko(g_log_fp, (off_t)g_offset, SEEK_SET) != 0) {
        log_msg("process_all_available_lines: fseeko failed: %s", strerror(errno));
        return -1;
    }
    int consumed = 0;
    char *line = NULL; size_t cap = 0; ssize_t n;
    while ((n = getline(&line, &cap, g_log_fp)) > 0) {
        if (gmseal_on_line_from_logbuf(line, (size_t)n) != 0) {
            log_msg("process_all_available_lines: on_line failed");
            free(line); return -1;
        }
        consumed++;
    }
    free(line);
    return consumed;
}

/* 初始化 */
static int gmseal_init(void)
{
    memset(g_chain,0,sizeof(g_chain));
    g_lines = 0; g_offset = 0; g_degraded_mode = 0;

    (void)open_idx_handle();
    (void)open_log_handle();

    int vs = 0;
    if (access(SEAL_PATH, R_OK) != 0) {
        log_msg("no existing seal: treat as first-run");
        vs = 1;
    } else {
        /* 有 seal 时：仅按 idx 尾恢复，不做 rebuild（保持证据链稳定） */
        vs = 0;
    }

    if (vs > 0) {
        unsigned long long idx_lines = idx_count_lines();
        if (idx_lines == 0) {
            unsigned char new_chain[32]; unsigned long long new_lines=0, new_off=0;
            if (rebuild_idx_from_log_atomic(LOG_PATH, IDX_PATH, new_chain, &new_lines, &new_off) != 0) {
                log_msg("rebuild_idx_from_log_atomic failed (first run)"); return -1;
            }
            if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
                log_msg("recover_tail_from_idx failed after first-run rebuild"); return -1;
            }
            log_msg("first-run: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
        } else {
            if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
                log_msg("recover_tail_from_idx failed (existing idx)"); return -1;
            }
            log_msg("resume from existing idx: lines=%llu off=%llu", (unsigned long long)g_lines, (unsigned long long)g_offset);
        }
    } else {
        if (recover_tail_from_idx(&g_lines, &g_offset, g_chain) != 0) {
            log_msg("recover_tail_from_idx failed"); return -1;
        }
        log_msg("verified or degraded: lines=%llu off=%llu (no rebuild)", (unsigned long long)g_lines, (unsigned long long)g_offset);
    }

    (void)check_rotation_and_rollover();
    return 0;
}

/* stdin 驱动：audispd 给我们喂数据（用作“时钟”触发） */
static int stdin_event_loop(void)
{
    char *line = NULL; size_t cap = 0; ssize_t n;
    while (g_run && (n = getline(&line, &cap, stdin)) != -1) {
        (void)n;

        (void)check_rotation_and_rollover();

        int rc = process_all_available_lines();
        if (rc < 0) {
            trigger_alarm("gmseal plugin processing error - exiting");
            free(line); return -1;
        }
        if (!g_degraded_mode && ALWAYS_REFRESH_SEAL && rc > 0) {
            if (gmseal_write_seal() != 0) log_msg("gmseal_write_seal failed");
        }
    }
    if (line) free(line);
    return 0;
}

/* main */
int main(int argc, char **argv)
{
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();

    log_open();
    log_msg("audisp-gmseal started (LOG=%s IDX=%s SEAL=%s)", LOG_PATH, IDX_PATH, SEAL_PATH);

    if (acquire_singleton_lock() != 0) {
        if (g_log) fclose(g_log);
        EVP_cleanup();
        ERR_free_strings();
        return 0;
    }

    if (gm_crypto_init() != 0) {
        log_msg("gm_crypto_init failed");
        fprintf(stderr, "[gmseal] gm_crypto_init failed\n");
        release_singleton_lock();
        if (g_log) fclose(g_log);
        EVP_cleanup();
        ERR_free_strings();
        return 2;
    }

    signal(SIGINT, on_signal);
    signal(SIGTERM, on_signal);

    if (gmseal_init() != 0) {
        log_msg("gmseal_init failed (abort)");
        gm_crypto_cleanup();
        release_singleton_lock();
        if (g_log) fclose(g_log);
        EVP_cleanup();
        ERR_free_strings();
        return 2;
    }

    /* 启动后先把当前可读尾部吃完，再写首次 seal（仅预检，不对齐） */
    {
        int ate = process_all_available_lines();
        if (ate < 0) {
            log_msg("initial tail scan failed");
        }
    }
    if (!g_degraded_mode) {
        if (gmseal_write_seal() != 0) {
            log_msg("initial seal write failed");
        }
    } else {
        log_msg("initial: degraded mode active, skip initial seal");
    }

    if (stdin_event_loop() != 0) {
        log_msg("stdin_event_loop aborted due to errors");
    }

    (void)gmseal_write_seal(); /* 降级态会跳过 */
    (void)check_rotation_and_rollover();

    close_idx_handle();
    close_log_handle();
    gm_crypto_cleanup();
    release_singleton_lock();
    if (g_log) fclose(g_log);
    EVP_cleanup();
    ERR_free_strings();
    log_msg("audisp-gmseal exited");
    return 0;
}
